欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

创建线程安全的单例模式

程序员文章站 2022-07-14 09:17:35
...

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();
        }
    }
输出:
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]

加上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

输出:
[email protected]
[email protected]
[email protected]
[email protected]
[email protected]

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

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;