欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

设计模式 - 单件模式 - 懒人模式

最编程 2024-04-17 08:30:34
...

懒汉模式:顾名思义就是只在第一次创建实例的时候,创建实例,能不创建就不创建.

class Singletionlazy {
    private static Singletionlazy instance = null;

    public static Singletionlazy getInstance() {
        // 只有当没有实例的时候,才会new出一个实例
        if(instance == null) {
            instance = new Singletionlazy();
        }
        return instance;
    }
    // 禁止new出实例
    private Singletionlazy() {

    }
}


public class Demo18 {
    public static void main(String[] args) {
        Singletionlazy s1 = Singletionlazy.getInstance();
        Singletionlazy s2 = Singletionlazy.getInstance();

        System.out.println(s1 == s2);
    }
}

结果为: true.

可是仔细观察,饿汉模式和懒汉模式是不是哪个或者全部有问题呢?
相信你们也能看出来.懒汉模式是线程不安全的!!
在饿汉模式中,通过getInstance方法只return instance对象就可以了.
但是在懒汉模式中,还有一步new 对象的操作,多线程进行肯定时不安全的.
我画个图供赏析.

在这里插入图片描述
那我们如何解决这个问题呢?

加锁

public static Singletionlazy getInstance() {
        // 只有当没有实例的时候,才会new出一个实例
        synchronized (Singletionlazy.class) {
            if(instance == null) {
                instance = new Singletionlazy();
            }
        }
        return instance;
    }

在这里插入图片描述
这里虽然解决了new多个对象的问题,但是加锁是一个成本比较高的操作,加锁可能会引起阻塞等待.如果无脑加锁,就会使执行效率收到影响.

我们加锁了,但是后面调用的时候,都会加锁,这样很影响程序的效率.
我们说的懒汉模式线程不安全,主要是在第一次new对象的时候,后续根据if就不会进行new对象了,也就不再需要锁了.

调整加锁

public static Singletionlazy getInstance() {
        // 只有当没有实例的时候,才会new出一个实例
        if (instance == null) {
            synchronized (Singletionlazy.class) {
                if(instance == null) {
                    instance = new Singletionlazy();
                }
            }
        }
        return instance;
    }

我们在加锁之间加一个if判断,当instance为空的时候,才加锁.
这样既没有线程安全问题,程序效率也提升了.

是不是以为已经没有问题了…
考虑到加锁操作可能会阻塞,阻塞多久我们也不知道,有可能Instance此时就被更改了!!
就又出现了线程安全问题!!
也就是内存可见性问题,也是引起线程不安全的问题之一.

内存可见性和指令重排序

仔细分析,这里会不会出现内存可见性问题,只是会有这样的风险,编译器是否会采取优化,不好说,所以我们加上volatile是保险操作.
 private volatile static Singletionlazy instance = null;

此处volatile还有另外一个作用,指令重排序问题.

// 可以分为3步
instance = new Singletionlazy();
  1. 给对象创建出内存空间.得到内存地址.
  2. 调用构造方法,对对象进行初始化.
  3. 把内存地址赋给Instance引用.

如果我们在第2步之前进行第3步,还没有给对象初始化,就调度给别的线程了,
我们再利用这个对象调用方法,属性的时候,因为没有初始化,就会出现问题,
加入volatile后,就解决了这个问题.

推荐阅读