创建线程安全的单例模式

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/obession/article/details/88918414

1、饿汉模式–类加载就实例化–线程安全

package SingleInstance;

/**
 * 饿汉式单例模式:实例化类就加载实例-天生线程安全
 */
public class SingleInstance {
    //私有化构造方法
    private SingleInstance(){}
    //创建实例
    private static SingleInstance instance = new SingleInstance();
    //提供实例获得方法
    public static SingleInstance getInstance(){
        return instance;
    }
}

2、懒汉式单例–需要的时候在实例化–非线程安全

package SingleInstance;

/**调用get方法的时候判断实例对象是否为空,为空在实例化,有线程安全问题。
 */
public class SingleInstance_lazy {
    private SingleInstance_lazy(){}
    private static SingleInstance_lazy singleInstance_lazy = null;
    public static SingleInstance_lazy getSingleInstance_lazy(){
        if (singleInstance_lazy==null){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleInstance_lazy = new SingleInstance_lazy();
        }
        return singleInstance_lazy;
    }
}
调用方法:
    public static void lazysingletest(){
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                public void run() {
                    SingleInstance_lazy singleInstance_lazy = SingleInstance_lazy.getSingleInstance_lazy();
                    System.out.println(singleInstance_lazy);
                }
            }).start();
        }
    }
输出:
SingleInstance.SingleInstance_lazy@11f3ef5e
SingleInstance.SingleInstance_lazy@731f7d35
SingleInstance.SingleInstance_lazy@1fe9b76c
SingleInstance.SingleInstance_lazy@11f3ef5e
SingleInstance.SingleInstance_lazy@7c2aa267

加上synchronized 关键字

public class SingleInstance_lazy {
    private SingleInstance_lazy(){}
    private static SingleInstance_lazy singleInstance_lazy = null;
    public static synchronized SingleInstance_lazy getSingleInstance_lazy(){
        if (singleInstance_lazy==null){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            singleInstance_lazy = new SingleInstance_lazy();
        }
        return singleInstance_lazy;
    }
}

但是会有效率问题,线程会一直自旋等待。锁的粒度太大,我们考虑减少锁的代码块

public class SingleInstance_lazy {
    private SingleInstance_lazy(){}
    private static SingleInstance_lazy singleInstance_lazy = null;
    public static SingleInstance_lazy getSingleInstance_lazy() {
        if (singleInstance_lazy==null){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (SingleInstance_lazy.class){
                singleInstance_lazy = new SingleInstance_lazy();
            }
        }
        return singleInstance_lazy;
    }
}

但是又会出现线程安全性问题,假如两个线程同时判断为null

输出:
SingleInstance.SingleInstance_lazy@7c2aa267
SingleInstance.SingleInstance_lazy@11f3ef5e
SingleInstance.SingleInstance_lazy@731f7d35
SingleInstance.SingleInstance_lazy@24e0614d
SingleInstance.SingleInstance_lazy@1fe9b76c

也没有实现单例的效果
所以考虑优化成双重判断:

public class SingleInstance_lazy {
    private SingleInstance_lazy(){}
    private static SingleInstance_lazy singleInstance_lazy = null;
    public static SingleInstance_lazy getSingleInstance_lazy() {
        if (singleInstance_lazy==null){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (SingleInstance_lazy.class){
                if (singleInstance_lazy==null)
                singleInstance_lazy = new SingleInstance_lazy();
            }
        }
        return singleInstance_lazy;
    }
}

但是这样也不能保证百分百不存在安全性问题。
指令重排序可能导致代码执行顺序并不是按照上面代码的顺序。
在new操作的时候,
1、分配内存;
2、内存初始化单例SingleInstance_lazy对象;
3、内存地址赋值给SingleInstance_lazy变量;
优化过后:
1、分配内存;
2、内存地址赋值给SingleInstance_lazy变量;
3、内存初始化单例SingleInstance_lazy对象;
那么在线程1在执行getSingleInstance_lazy方法的时候,执行new到的过程到内存地址分配到SingleInstance_lazy变量,突然切换线程2,线程2在第一个判空过程就会得到SingleInstance_lazy!=null,然而只是一个空指针。

线程1
第二个instans==null?
分配内存
内存地址赋值变量M
内存初始化对象
线程2
第一个instans==null?
分配内存
内存地址赋值变量M
内存初始化对象

所以加上关键字volatile,保证不让编译器不优化顺序

private static volatile SingleInstance_lazy singleInstance_lazy;
展开阅读全文

没有更多推荐了,返回首页