JAVA单例模式

一:懒汉模式,线程不安全

懒加载(在需要的时候创建对象)

public class Singleton1 {
    private static Singleton1 instance;
    private Singleton1(){}
    public static Singleton1 getInstance(){
        if(instance == null){
            instance = new Singleton1();
        }
        return instance;
    }
}

二:懒汉模式,线程安全

/**
 * 懒汉模式,线程安全
 * */
public class Singleton2 {
    private static Singleton2 singleton2;
    private Singleton2(){}
    public static synchronized Singleton2 getInstance(){
        if(singleton2==null){
            singleton2=new Singleton2();
        }
        return singleton2;
    }
}

效率极为低下,不建议使用

三:饿汉模式,线程安全

public class Singleton3 {
    private static Singleton3 singleton3=new Singleton3();
    private Singleton3(){}
    public static synchronized Singleton3 getInstance(){
        return singleton3;
    }
}

没有达到懒加载的目的,不推荐

 

四:饿汉模式,线程安全

public class Singleton4 {
    private static Singleton4 instance ;
    static {
        instance = new Singleton4();
    }
    private Singleton4(){}
    public static synchronized Singleton4 getInstance(){
        return instance;
    }
}

等同于第三种

 

五:静态内部类,线程安全

public class Singleton5 {
    private Singleton5(){}
    public static Singleton5 getInstance(){
        return SingletonHolder.INSTANCE;
    }
    private static class SingletonHolder{
        private static Singleton5 INSTANCE=new Singleton5();
    }
}

与第三第四种的区别是:前者在类加载时就会创建实例,假如实例耗费内存,则是一种不明智的选择,后者则会懒加载

 

第六种:枚举类,线程安全

  public enum Singleton6 {
    INSTANCE;
    public void whateverMethod(){}
    }

安卓推荐,不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象

 

第七种:双重校验锁,线程安全

public class Singleton7 {

    private Singleton7() {}; // 私有化构造方法
    private static volatile Singleton7 singleTon = null;
    public static Singleton7 getInstance() {
        // 第一次校验
        if (singleTon == null) {
            synchronized (Singleton7.class) {
                // 第二次校验
                if (singleTon == null) {
                    singleTon = new Singleton7();
                }
            }
        }
        return singleTon;
    }
}

分析:

  第一次校验:由于单例模式只需要创建一次实例,如果后面再次调用getInstance方法时,则直接返回之前创建的实例,因此大部分时间不需要执行同步方法里面的代码,大大提高了性能。如果不加第一次校验的话,那跟上面的懒汉模式没什么区别,每次都要去竞争锁。

  第二次校验:如果没有第二次校验,假设线程t1执行了第一次校验后,判断为null,这时t2也获取了CPU执行权,也执行了第一次校验,判断也为null。接下来t2获得锁,创建实例。这时t1又获得CPU执行权,由于之前已经进行了第一次校验,结果为null(不会再次判断),获得锁后,直接创建实例。结果就会导致创建多个实例。所以需要在同步代码里面进行第二次校验,如果实例为空,则进行创建。

  为什么要加volatile关键字呢?

  需要注意的是,private static volatile SingleTon3 singleTon=null;需要加volatile关键字,否则会出现错误。问题的原因在于JVM指令重排优化的存在。在某个线程创建单例对象时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化。若紧接着另外一个线程来调用getInstance,取到的就是状态不正确的对象,程序就会出错。

  详细解释一下为什么要使用volatile

public class Singleton {
    private static Singleton s;
    private Singleton(){};
    public static Singleton getInstance() {  //1
        if(s == null) { //2
            synchronized (Singleton.class) { //3
                if(s == null) { //4
                    s = new Singleton(); //5
                }
            }
        }
        return s; //6
    }
}

以前不了解为什么需要volatile关键字,后来发现在并发情况下,如果没有volatile关键字,在第5行会出现问题
对于第5行 s = new Singleton(); //5
可以分解为3个步骤:

1 memory=allocate();// 分配内存 相当于c的malloc
2 ctorInstanc(memory) //初始化对象
3 s=memory //设置s指向刚分配的地址

上面的代码在编译器运行时,可能会出现重排序 从1-2-3 排序为1-3-2

如此在多线程下就会出现问题

例如现在有2个线程A,B

线程A在执行第5行代码时,B线程进来,而此时A执行了 1和3,没有执行2,此时B线程判断s不为null (语句2)直接返回一个未初始化的对象,就会出现问题

 

posted @ 2019-07-26 15:46  沦为旧友  阅读(186)  评论(0编辑  收藏  举报