单例模式的懒汉式为什么是线程不安全的,懒汉式如何实现线程安全

目录

懒汉式为什么是线程不安全的

懒汉模式实现线程安全

1.对实例化方法加锁

2.double check+volatile方案


单例模式是工作中高频使用的设计模式之一。单例模式可以确保内存中单例类只有一个实例,有效的减少了内存的开销,避免了类的重复创建和销毁。

懒汉式为什么是线程不安全的

单例模式有两种实现方式,即技术人员所熟知的饿汉式和懒汉式。二者的区别是创建实例的时机,饿汉式在应用启动时就创建了 实例,饿汉式是线程安全的,是绝对单例的。懒汉式在对外提供的获取方法被调用时会实例化对象。在多线程情况下,懒汉模式不是线程安全的,示范代码如下:

public class LazySingleTon {
    private static  LazySingleTon lazySingleTon = null;
    private LazySingleTon(){
    }
    public static LazySingleTon getInstance(){
        if(lazySingleTon == null){
            //对下面的代码加断点    
            lazySingleTon = new LazySingleTon();
        }
        System.out.println(lazySingleTon);
        return lazySingleTon;
    }

    public static void main(String[] args) {
        for(int i = 1 ; i <= 2 ;i ++ ){
            Thread thread = new ForTestThread();
            thread.start();
        }
    }
 }
public class ForTestThread extends Thread {
    @Override
    public void run() {
        LazySingleTon lazySingleTon = LazySingleTon.getInstance();
    }
}

操作如下

配合idea的多线程debug,执行main方法,在实例化处加断点。当第一个线程执行验证了lazySingleTon对象为null,准备new一个新对象时第二个线程也验证完了lazySingleTon为null,这时两个线程都会实例化LazySingleTon,执行完成输出的内容:

Connected to the target VM, address: '127.0.0.1:49751', transport: 'socket'
com.rbac.console.singleton.LazySingleTon@492dccc0
Disconnected from the target VM, address: '127.0.0.1:49751', transport: 'socket'
com.rbac.console.singleton.LazySingleTon@2f17749c

打印内容显示同一个内存中存在两个实例分别是:LazySingleTon@2f17749c、LazySingleTon@492dccc0。这表明在多线程情况下有一定几率会出现一个单例类会有多个实例,证明懒汉式是非线程安全的。

懒汉模式实现线程安全

1.对实例化方法加锁

 public static synchronized  LazySingleTon getInstance(){
        if(lazySingleTon == null){
            lazySingleTon = new LazySingleTon();
        }
        System.out.println(lazySingleTon);
        return lazySingleTon;
    }

加锁可以确保实例话方法只能有一个线程执行,确保两个线程不会同时进入实例化方法。缺点是同步锁的加锁和解锁比较消耗资源,而且synched关键字修饰static方法时锁的是整个class,对性能会有影响

2.double check+volatile方案

public static  LazySingleTon getInstance(){
        if(lazySingleTon == null){
            synchronized (LazySingleTon.class) {
                if (lazySingleTon == null) {
                    //1.分配内存
                    //2.初始化对象
                    //3.设置LazySingleTon类执行内存
                    lazySingleTon = new LazySingleTon();
                }
            }
        }
        System.out.println(lazySingleTon);
        return lazySingleTon;
    }

double check方案可以实现线程安全,且降低内存消耗。代码中三行注释代表了new对象时底层进行了三步操作,由于JVM优化算法可能会指令重排,也就是第二步和第三步执行的顺序会互换。这样可能会出现第一个第一个线程出现了指令重排情况,还没来得及初始化对象,第二个线程就进行了对象获取,导致获取到的对象是null。

为避免这种情况的发生可以使用volatile关键字修饰静态类,禁止指令重排

    private volatile static  LazySingleTon lazySingleTon = null;

也可以使用静态内部类的初始化延迟加载解决,静态内部类有初始化锁,达到线程安全的目的

public class LazySingleTon {
    private LazySingleTon(){
    }
    public static  LazySingleTon getInstance(){
        return  InnerClass.lazySingleTon;
    }

    private static class InnerClass{
        private  static LazySingleTon lazySingleTon = new LazySingleTon();
    }

    public static void main(String[] args) {
        for(int i = 1 ; i <= 2 ;i ++ ){
            Thread thread = new ForTestThread();
            thread.start();
        }
    }
 }

文章内容参考自慕课网


                                                          这位小可爱,如果觉得文章不错,请关注或点赞    (-__-)谢谢

 

发布了56 篇原创文章 · 获赞 67 · 访问量 13万+

猜你喜欢

转载自blog.csdn.net/leo187/article/details/104259750