转载:https://blog.csdn.net/goodlixueyong/article/details/51935526

https://www.cnblogs.com/cielosun/p/6582333.html

http://www.importnew.com/21141.html

序列化和反序列化:https://blog.csdn.net/cselmu9/article/details/51366946

1、单例模式

特点:

1)单例类只能有一个实例。
2)单例类必须自己创建自己的唯一实例。
3)单例类必须给所有其他对象提供这一实例。

实现要素:1)私有静态属性 2)私有构造方法 3)公有静态工厂方法

2、单例实现

2.1懒汉式

懒加载模式(使用时才实例化)。

public class SingletonTest {
/**
* 懒汉式,非线程安全
* @param args
*/
private static SingletonTest singletonTest=null;//私有静态属性
private SingletonTest(){}//私有构造方法
public static SingletonTest getInstance(){//公有静态方法
if(singletonTest==null){
singletonTest=new SingletonTest();
}
return singletonTest;
}
}

存在问题:

1)通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。

2)线程不安全的,并发环境下很可能出现多个Singleton实例。

测试代码:

public class SingletonTest {
/**
* 懒汉式,非线程安全
* @param args
*/
private static SingletonTest singletonTest=null;//私有静态属性
private SingletonTest(){}//私有构造方法
public static SingletonTest getInstance(){//公有静态方法
if(singletonTest==null){
singletonTest=new SingletonTest();
}
return singletonTest;
} public static void main(String[] args){//多线程测试
ExecutorService executor=Executors.newFixedThreadPool(10);
for(int i=0;i<5;i++){
executor.execute(
new Runnable(){
public void run(){
System.out.println(SingletonTest.getInstance().hashCode());
}
}
);
}
}
}

测试结果

1711790973
1711790973
1711790973
1711790973
1711790973

测试结果并未显示创建了多个实例对象,关于懒汉式非线程安全是否正确?

1)网上有增加sleep方法验证其非线程安全:

增加方法一:

public class SingletonTest {
/**
* 懒汉式,非线程安全
* @param args
*/
private static SingletonTest singletonTest=null;//私有静态属性
private SingletonTest(){}//私有构造方法
public static SingletonTest getInstance(){//公有静态方法
if(singletonTest==null){
try {
Thread.sleep(100);
singletonTest=new SingletonTest();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return singletonTest;
} public static void main(String[] args){//多线程测试
ExecutorService executor=Executors.newFixedThreadPool(20);
for(int i=0;i<10;i++){
executor.execute(
new Runnable(){
public void run(){
System.out.println(SingletonTest.getInstance().hashCode());
}
}
);
}
}
}

这种情况下,多线程进入阻塞状态,自然会导致创建多个实例(但生产中应该不会这么处理),个人觉得不能验证其线程安全问题。运行结果如下:

2006466913
425058492
753697950
1427465122
1542895644
1582065822
1786107484
925527685
1716687590
1866484817

增加方法二:

public class SingletonTest {
/**
* 懒汉式,非线程安全
* @param args
*/
private static SingletonTest singletonTest=null;//私有静态属性
private SingletonTest(){}//私有构造方法
public static SingletonTest getInstance(){//公有静态方法
if(singletonTest==null){
singletonTest=new SingletonTest();
}
return singletonTest;
} public static void main(String[] args){//多线程测试
ExecutorService executor=Executors.newFixedThreadPool(200);
for(int i=0;i<1000;i++){//线程数10000
executor.execute(
new Runnable(){
public void run(){
try {
Thread.sleep(300);//增加sleep方法
System.out.println(SingletonTest.getInstance().hashCode());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
);
}
}
}

这种情况下,和生产环境接近(获取对象前进行其他处理,但如果非sleep方式,而是普通的处理会是什么情况)。运行结果如下:

1183896518
1183896518
1183896518
1183896518
1183896518
1183896518
1183896518
1183896518
1324694881
1183896518
1324694881
1939292200

出现了创建多个实例现象。

2)但为什么在没有sleep方法情况下,并未出现创建多个实例问题?

分析1:可能是线程太少,能够处理的过来。为验证,增加线程数为1000和10000。

public class SingletonTest {
/**
* 懒汉式,非线程安全
* @param args
*/
private static SingletonTest singletonTest=null;//私有静态属性
private SingletonTest(){}//私有构造方法
public static SingletonTest getInstance(){//公有静态方法
if(singletonTest==null){
singletonTest=new SingletonTest();
}
return singletonTest;
} public static void main(String[] args){//多线程测试
ExecutorService executor=Executors.newFixedThreadPool(2000);
for(int i=0;i<10000;i++){//线程数10000
executor.execute(
new Runnable(){
public void run(){
System.out.println(SingletonTest.getInstance().hashCode());
}
}
);
}
}
}

执行结果:

1181470557
1181470557
1181470557
1181470557
1181470557

线程数为10000也没有创建多个对象。

结论:待进一步了解。注意:executor并不是一个线程在跑。可能需要放到服务器处理。

2.2饿汉式

类加载时实例化,天生线程安全。

public class SingletonTest {
/**
* 饿汉式(天生线程安全,类加载时实例化)
* @param args
*/
private static final SingletonTest singletonTest=new SingletonTest();//私有静态final属性
private SingletonTest(){};
public static SingletonTest getInstance(){
return singletonTest;
} public static void main(String[] args){//多线程测试
ExecutorService executor=Executors.newFixedThreadPool(20);
for(int i=0;i<100;i++){//线程数1000
executor.execute(
new Runnable(){
public void run(){
System.out.println(SingletonTest.getInstance().hashCode());
}
}
);
}
}

单例的实例被声明成 static 和 final 变量。缺点是:不是一种懒加载模式(lazy initialization)。在一些场景中无法使用,如Singleton 实例的创建是依赖参数或者配置文件的,在 getInstance() 之前必须调用某个方法设置参数给它,那样这种单例写法就无法使用了。

2.3懒汉式线程安全

public class SingletonTest {
/**
* 线程安全单例之synchronized
*/
private static SingletonTest singletonTest=null;
private SingletonTest(){};
public static synchronized SingletonTest getInstance(){
if(singletonTest==null){
singletonTest=new SingletonTest();
}
return singletonTest;
}
}

缺点:效率低。在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。

2.4懒汉式双重检验锁

基于2.3缺点,引出了2.4方式。线程安全。

public class SingletonTest {
/**
* 线程安全单例之双重校验
*/
private static volatile SingletonTest singletonTest=null;
public static SingletonTest getInstance(){
if(singletonTest==null){//
synchronized(SingletonTest.class){
if(singletonTest==null){//2
//原因:多个线程一起进入同步块中,如果不进行二次检验则可能创建多个实例
singletonTest=new SingletonTest();
}
}
}
return singletonTest;
} public static void main(String[] args){//多线程测试
ExecutorService executor=Executors.newFixedThreadPool(20);
for(int i=0;i<100;i++){//线程数100
executor.execute(
new Runnable(){
public void run(){
System.out.println(SingletonTest.getInstance().hashCode());
}
}
);
}
}
}

双重检查锁,因为会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。

注意:volatile修饰符。主要在于instance = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。

  1. 给 instance 分配内存
  2. 调用 Singleton 的构造函数来初始化成员变量
  3. 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

我们只需要将 instance 变量声明成 volatile 就可以了。volatile实现2个功能:实现可见性和禁止指令重排优化。

2.5静态内部类单例实现

线程安全。

public class SingletonTest {
/**
* 静态内部类方式(避免锁性能,又实现线程安全)
* 本质和饿汉式一样
* @param args
*/
private static class SingleClass{//私有静态内部类
private static final SingletonTest instance=new SingletonTest();//私有静态final属性
}
public static final SingletonTest getInstance(){//公有静态final方法
return SingleClass.instance;
} public static void main(String[] args){//多线程测试
ExecutorService executor=Executors.newFixedThreadPool(20);
for(int i=0;i<100;i++){//线程数100
executor.execute(
new Runnable(){
public void run(){
System.out.println(SingletonTest.getInstance().hashCode());
}
}
);
}
}

2.6枚举Enum单例实现

public enum EasySingleton {
INTSTANCE;
public void otherMethods(){
System.out.println("Something");
} public static void main(String[] args){
ExecutorService executor=Executors.newFixedThreadPool(20);
for(int i=0;i<100;i++){//线程数100
executor.execute(
new Runnable(){
public void run(){
System.out.println(EasySingleton.INTSTANCE.hashCode());
EasySingleton.INTSTANCE.otherMethods();//调用方法
}
}
);
}
}
}

优点:创建简单。

1)可以通过EasySingleton.INSTANCE来访问实例,这比调用getInstance()方法简单多了。

2)创建枚举默认就是线程安全的

3)防止反序列化导致重新创建新的对象。但是还是很少看到有人这样写,可能是因为不太熟悉吧。

转载:java基础之单例的更多相关文章

  1. Java基础系列-单例的7种写法

    原创文章,转载请标注出处:https://www.cnblogs.com/V1haoge/p/10755322.html 一.概述 Java中单例有7种写法,这个是在面试中经常被问到的内容,而且有时候 ...

  2. java基础28 单例集合Collection下的List和Set集合

    单例集合体系: ---------| collection  单例集合的根接口--------------| List  如果实现了list接口的集合类,具备的特点:有序,可重复       注:集合 ...

  3. java基础27 单例集合Collection及其常用方法

    1.集合 集合是存储对象数据的集合容器 1.1.集合比数组的优势 1.集合可以存储任意类型的数据,数组只能存储同一种数据类型的数据    2.集合的长度是变化的,数组的长度是固定的 1.2.数组:存储 ...

  4. Java设计模式之单例

    一.Java中的单例: 特点: ① 单例类只有一个实例 ② 单例类必须自己创建自己唯一实例 ③ 单例类必须给所有其他对象提供这一实例 二.两种模式: ①懒汉式单例<线程不安全> 在类加载时 ...

  5. JAVA基础英语单词表(下)

    quantity                     / 'kwɔntiti /                    量,数量 query                             ...

  6. Java复习11. 单例编程

    Java复习11. 单例编程 1.最简单的写法,那个方式是线程不安全的 public class Singleton {     private static Singleton instance; ...

  7. Java 多线程之单例设计模式

    转载:https://segmentfault.com/a/1190000007504892 概念: Java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍两种:懒汉式单例.饿汉 ...

  8. (转载)Java基础知识总结

    写代码: 1,明确需求.我要做什么? 2,分析思路.我要怎么做?1,2,3. 3,确定步骤.每一个思路部分用到哪些语句,方法,和对象. 4,代码实现.用具体的java语言代码把思路体现出来. 学习新技 ...

  9. java基础36 双例集合Map下的HashMap和TreeMap集合

    单例集合体系: ---------| collection  单例集合的根接口--------------| List  如果实现了list接口的集合类,具备的特点:有序,可重复       注:集合 ...

随机推荐

  1. Netty核心概念(8)之Netty线程模型

    1.前言 第7节初步学习了一下Java原本的线程池是如何工作的,以及Future的为什么能够达到其效果,这些知识对于理解本章有很大的帮助,不了解的可以先看上一节. Netty为什么会高效?回答就是良好 ...

  2. python的强制转换(当出现 not supported between instances of 'str' and 'int' 的错误时)

    当我们编程时,有时会出现如下错误:TypeError: '>' not supported between instances of 'str' and 'int' 如下图: 这是因为input ...

  3. JavacProcessingEnvironment类解读

    JavacProcessingEnvironment类的继承体系如下: 其中含有很多内部类,最重要的是迭代注解处理器相关的类,如下:

  4. 一个非常好用的图片切割工具(c# winform开发)

    本人业余时间开发了一个图片切割工具,非常好用,也很灵活! 特别对大型图片切割,更能体现出该软件的优势! 功能说明 可以设定切割的高度和宽度.切割线可以上下拖动,可以增加一个切割区域,可设定某个区域不参 ...

  5. 使用Pelican在Github(国外线路访问)和Coding(国内线路访问)同步托管博客

    本文原文地址:使用Pelican在Github(国外线路访问)和Coding(国内线路访问)同步托管博客 介绍: Github Pages 禁用了百度爬虫,因此百度搜索引擎经常抓取不到在Github上 ...

  6. Visual Studio使用阿里云Code Git服务器的常见问题

    使用Github的服务器太慢,阿里的https://code.aliyun.com的国内服务器还是很快的.但是使用阿里的Git服务器总是有些地方出问题,现记录下常见的问题: 1.如提示源码已在TFS管 ...

  7. 常用算法2 - 广度优先搜索 & 深度优先搜索 (python实现)

    1. 图 定义:图(Graph)是由顶点的有穷非空集合和顶点之间边的集合组成,通常表示为:G(V,E),其中,G表示一个图,V是图G中顶点的集合,E是图G中边的集合. 简单点的说:图由节点和边组成.一 ...

  8. Linux errno详解

    1. 错误码 / errno Linux中系统调用的错误都存储于 errno中,errno由操作系统维护,存储就近发生的错误,即下一次的错误码会覆盖掉上一次的错误. PS: 只有当系统调用或者调用li ...

  9. 【手记】解决启动SQL Server Management Studio 17时报Cannot find one or more components...的问题

    刚装好SSMS 17.1准备体验,弹出: 一番搜索,普遍办法都是安装VS2015独立shell.删除某个注册表项什么的,没用,首先这个shell我是装了的,然后也没有那个注册表项.我自己尝试过重装sh ...

  10. 从数据库表中随机获取N条记录的SQL语句

    Oracle: select * from (select * from tableName order by dbms_random.value) where rownum < N MS SQ ...