浅谈单例模式的几种创建方式

来源:互联网 发布:淘宝安卓平板电脑 编辑:程序博客网 时间:2024/05/18 03:52

1、单线程环境下可可考虑的方式

仅适用于单线程,非线程安全

package Singleton;/** * Created by qj 2017/9/3. */public class Singleton1 {    private static Singleton1 instance = null;    private Singleton1() {    }    public static Singleton1 getInstance() {        if (instance == null) instance = new Singleton1();        return instance;    }}

2、用synchronized关键字锁修饰方法

线程安全,但不利于高并发

package Singleton;/** * Created by qj on 2017/9/3. */public class Singleton2 {    private static Singleton2 instance = null;    private Singleton2() {    }    public static synchronized Singleton2 getInstance() {        if (instance == null) instance = new Singleton2();        return instance;    }}

3、加载时初始化(1)

线程安全,但不能懒加载

package Singleton;/** * Created by qj on 2017/9/3. */public class Singleton3 {    private static Singleton3 instance = new Singleton3();    private Singleton3() {    }    public static Singleton3 getInstance() {        return instance;    }}

4、静态内部类。加载时初始化(二)

线程安全,可以实现懒加载。

package Singleton;/** * Created by qj on 2017/9/3. */public class Singleton4 {    private Singleton4() {    }    public static Singleton4 getInstance() {        return InitSingleton.instance;    }    private static class InitSingleton {        private final static Singleton4 instance = new Singleton4();    }}

5、使用枚举类

线程安全,也可懒加载。比较好的实现方式

package Singleton;/** * Created by qj on 2017/9/3. */public enum Singleton5 {    INSTANCE;    Singleton5() {        // INIT    }    public static Singleton5 getInstance() {        return INSTANCE;    }}

6、DCL 双重校验锁

之前一直也有看到网上分析说这种方式创建单例在多线程情况下不安全。后来看了一些书,结合自己的理解,搞了几十分钟才搞明白。网上很多分析多线程不安全的原因,我觉得长篇大论的看了之后也没太大收获,这里结合代码简明扼要的说一下多线程下不安全的原因。

阅读后续内容需要的知识:
1、先行发生(happens-before)原则
2、指令重排序相关
3、volatile 语义(可见性、屏蔽指令重排序(jdk1.5+) 注意:volatile只保证(读)或(写)原子性,而不保证(读并且写)的原子性
4、final 屏蔽指令重排序

package Singleton;// 注意:这是一个错误示例/** * Created by qj on 2017/9/3. */public class Singleton6 {    private static Singleton6 instance = null;    private static int k = 0;    private Singleton6() {        k = 10;                                 // 5、setK    }    public static Singleton6 getInstance() {        if (instance == null) {                 // 1、Check1            synchronized (Singleton6.class) {   // 2、Syn                if (instance == null) {         // 3、Check2                    instance = new Singleton6();// 4、Create Obj                }            }        }        return instance;    }    public int getK() {        return k;                               // 6、getK    }}

代码如上,多线程情况下,可以确定的是,创建单例的过程4只能被执行一次。在同一个线程中,我们可以确切的说,5 6满足happens-before原则的。而多线程情况下则不一定,原因在于:

对象创建与赋值过程4 是非原子性操作,简化问题,我们可以说 过程4至少包含以下步骤:①、分配空间给新建对象 ②、初始化对象 ③、生成新建对象的引用 ④、将对象引用赋值给instance。在这四个过程中, 执行次序有 ①->③->④ 和①->② ,至于②和④的顺序,在单线程下对于运行结果是没有任何影响的,此时虚拟机可能会先④后②。而在这种情况下,假如线程A执行完④,让出CPU(此时instance不为null),此时线程B同样执行getInstance方法来获取单例,在Check1处判断到instance不为null,直接返回了instance,此时调用getK方法,变会产生错误=>得到的k值为0。

为了避免出现这种错误,可以采取的修正措施(既然问题是由于指令重排序造成的,那屏蔽掉指令重排序即可):
方法① 给instance变量加上volatile关键字
方法② 给初始化字段加上final关键字 (待确定)
建议方法①

最后附上正确代码

package Singleton;/** * Created by qj on 2017/9/3. */public class Singleton6 {    private volatile static Singleton6 instance = null;    private static int k = 0;    private Singleton6() {        k = 10;    }    public static Singleton6 getInstance() {        if (instance == null) {            synchronized (Singleton6.class) {                if (instance == null) {                    instance = new Singleton6();                }            }        }        return instance;    }    public int getK() {        return k;    }}