单例模式-懒汉式和饿汉式,懒汉式的DCL以及volatile

一、简介

单例模式,指的是某一个类,只允许实例出一个对象存在。而实现单例模式有懒汉式和饿汉式。饿汉式指的是在创建类时就初始化好对象,,而懒汉式指的是在需要使用到对象实例时,才进行初始化对象。

二、实现方式

饿汉式:

/**
 * 饿汉式单例模式
 */
public class HungerSingleton {

    private static HungerSingleton instance=new HungerSingleton();

    private HungerSingleton(){}

    public static HungerSingleton getInstance(){
        return instance;
    }
}

懒汉式(不考虑线程安全):

/**
 * 懒汉式单例
 */
public class LazySingleton {

    private static LazySingleton instance=null;

    private LazySingleton(){
        System.out.println("初始化LazySingleton..........");
    }

    public static LazySingleton getInstance(){
        if(instance==null)
            instance=new LazySingleton();
        return instance;
    }
}

三、懒汉式的线程不安全

懒汉式创建单例,在需要时才创建对象,因此在空间上更加友好。但也存在多线程的问题,比如线程1调用getInstance执行到

instance=new LazySingleton();

线程2也调用getInstance,并且判断出instance==null,也同样执行

instance=new LazySingleton();

这就导致了多线程下可能创建多个对象,这就不符合我们的预期了,测试代码如下:

public class Singleton {
    public static void main(String[] args) {
        for(int i=0;i<20;i++){
            new Thread(new Runnable() {
                public void run() {
                    LazySingleton.getInstance();
                }
            }).start();
        }
    }
}

四、DCL双端检测锁

使用双端检测锁,解决这种问题,代码如下:

/**
 * 懒汉式单例
 */
public class LazySingletonNew {

    private static LazySingletonNew instance=null;

    private LazySingletonNew(){
        System.out.println("初始化LazySingletonNew..........");
    }

    public static LazySingletonNew getInstance(){
        if(instance==null){
            synchronized (LazySingletonNew.class){
                if(instance==null)
                    instance=new LazySingletonNew();
            }
        }
        return instance;
    }
}

测试代码如下:

public class Singleton {
    public static void main(String[] args) {
        for(int i=0;i<20;i++){
            new Thread(new Runnable() {
                public void run() {
                    LazySingleton.getInstance();
                }
            }).start();
        }
        for(int i=0;i<20;i++){
            new Thread(new Runnable() {
                public void run() {
                    LazySingletonNew.getInstance();
                }
            }).start();
        }
    }
}

五、使用volatile+DCL

使用上述的DCL,似乎已经可以解决线程安全问题,而实际上上面的DCL仍然有出现问题的可能性,虽然说很小,分析如下:

instance=new LazySingletonNew();并非是原子操作,new创建对象分为三步:

1、内存中分配一段空间

2、初始化该空间

3、将instance指向该空间

而由于指令可能存在重排(编译器优化或者cpu优化),可能执行的顺序为1、 3、 2、

那么,可能存在如下:

线程1创建空间,在synchronize代码块中,new对象的指令顺序为1、 3、 2,并且未执行到3时由于线程调度切换为线程2,线程2再次判断if(instance==null),发现不为null,因此将未初始化的空间拿来用了。这时,调用一个未初始化的空间里面的方法,变量名等将可能导致错误。

因此,使用volatile禁止指令重排。

代码如下:

/**
 * 懒汉式单例
 */
public class LazySingletonNew {

    private static volatile LazySingletonNew instance=null;

    private LazySingletonNew(){
        System.out.println("初始化LazySingletonNew..........");
    }

    public static LazySingletonNew getInstance(){
        if(instance==null){
            synchronized (LazySingletonNew.class){
                if(instance==null)
                    instance=new LazySingletonNew();
            }
        }
        return instance;
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章