饿汉模式和懒汉模式

10 篇文章 0 订阅
4 篇文章 0 订阅

目录

设计模式

设计模式和框架的区别

单例模式

饿汉模式

代码

实现原理

为什么叫做饿汉模式

懒汉模式

代码

饿汉模式和懒汉模式在多线程环境下调用getInstance是否安全

如何让懒汉模式,能够成为线程安全的


设计模式

饿汉模式和懒汉模式都属于单例模式.

单例模式是设计模式的一种.那么,什么是设计模式??

通俗的将就是计算机的大佬们为了让普通程序员的代码不要写的太差而发明的一组"棋谱".

针对一些典型的场景,给出了一些典型的解决方案.

设计模式和框架的区别

框架是硬性的规则,是你不得不遵守的,不按照框架来写,代码跑不起来.

设计模式是软性的,不遵守,代码写出来也能跑,但是可能这个代码,可读性,可维护性,可拓展性都不好.

人们发明的设计模式有很多种(有些书上说"二十三种设计模式"),但其实是不止这些的,这个数字也是动态变化的,随着时代的发展,也会有更多的设计模式被发明出来,以此来应对一些新场景.

设计模式,本质上就是"规章制度",但是如果说脱离场景,来理解设计模式,其实是比较抽象的.

饿汉模式和懒汉模式是工作面试中和校招中比较常见的,因此我们要重点掌握.其他的模式,最好就是工作之后,结合公司的实际代码,来理解和掌握.


单例模式

单例模式=>单个实例(对象)

在有些特定的场景中,有的特定的类,只能创建出一个实例,不应创建出多个实例.

(注意:像这样的需求,不依赖单例模式,而是靠"君子协定",也是可以的)

使用了单例模式后,此时想创建多个实例,是比较困难的.

单例模式,就是针对上述的需求场景进行了更强制的保证,通过巧用Java的现有语法,达成了某个类,只能被创建出一个实例这样的效果.(当程序员不小心创建了多个实例,就会编译报错)

在Java中,单例模式有很多种,我们这里只介绍最常见的两种:饿汉模式和懒汉模式.


饿汉模式

代码

// 饿汉模式的 单例模式 实现.
// 此处保证 Singleton 这个类只能创建出一个实例.
class Singleton {
    // 在此处, 先把这个实例给创建出来了.
    private static Singleton instance = new Singleton();

    // 如果需要使用这个唯一实例, 统一通过 Singleton.getInstance() 方式来获取.
    public static Singleton getInstance() {
        return instance;
    }

    // 为了避免 Singleton 类不小心被复制出多份来.
    // 把构造方法设为 private. 在类外面, 就无法通过 new 的方式来创建这个 Singleton 实例了!!
    private Singleton() {}
}

public class ThreadDemo19 {
    public static void main(String[] args) {
        Singleton s = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s == s2);
    }
}

此时我们运行代码,发现是true,那就说明,s和s2指向的是同一个实例.

实现原理

 

加了static之后,这个属性就和实例无关了,而是和类相关!

Java代码中的每一个类,都会在编译完成后得到.class文件,JVM运行时就会加载这个.class文件读取其中的二进制指令,并且在内存中构造出对应的类对象.(形如Singleton.class)

由于类对象在一个Java进程里,只是有唯一一份的,因此类对象内部的类属性也是唯一一份了. 

构造方式设为private,类外无法new,就确保了这个实例是唯一的.

为什么叫做饿汉模式

类加载阶段,就把实例创建出来了(类加载阶段是比较考前的阶段),这种效果,给人一种"特别急切"的感觉,因此就叫做饿汉模式.

类加载:运行一个Java程序,就需要让Java进程能够找到并读取对应的.class文件,就会读取文件内容,进行解析,构造成类对象....这一系列的过程操作就称为类加载.


懒汉模式

代码

class SingletonLazy {
    private static SingletonLazy instance = null;

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

    private SingletonLazy() {}
}

public class ThreadDemo20 {
    public static void main(String[] args) {
        SingletonLazy s = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s == s2);
    }
}

 相比于饿汉模式

在懒汉模式中,SingletonLazy这个实例并非是类加载的时候就创建了,而是真正第一次使用的时候,才去创建.(如果不用,就不创建了)

这也使得,懒汉模式的效率会更高.


饿汉模式和懒汉模式在多线程环境下调用getInstance是否安全

 

饿汉模式这里,多线程调用只涉及"读操作",所以是线程安全的.


 

 懒汉模式这里,既有读又有写,在多线程的场景下,是线程不安全的.

 注意:这里new操作本质上也是多个指令,此处暂时视为一个整体,不影响对程序的分析.

由于两个线程读到的instance都是null,所以会触发多次new操作,显然就不是单例了,就存在安全问题了.


如何让懒汉模式,能够成为线程安全的

刚才出现线程安全问题的原因,本质是读,比较和写,这三个操作不是原子的,这就导致了t2读到的值可能是t1还没来得及写的,会产生脏读问题.

解决这个问题,就是要加锁!!!!

这样的加锁,是错误的,无效的.

 

把锁加到外头 ,此时才能保证读操作和修改操作是一个整体.

 


当前这样的代码,还存在一定的问题

每次调用getInstance都需要加锁,而加锁操作本身是有开销的,况且我们并不需要每次调用getInstance都要加锁.

这里的加锁只是在new出对象之前加上是有必要的,防止出现脏读的问题,一旦对象new完了,后续在调用getInstance,此时instance一定是一个非null的值,所以直接返回就好了,没必要在加锁.

因此,我们可以加上一个判定,来判断当前的instance是否是null,如果是就加锁,如果不是,就直接return就好了.

 

 第一个if条件是负责判定是否要加锁

第二个if是负责判定是否要创建对象.

这两个条件的目的是完全不同的.

如果这两个if条件中间没有加锁,连续两个相同的if,没有意义.但是有了加锁,就不一定了.

加锁操作可能会引起线程的阻塞,如果当前线程阻塞完之后,执行第二个if条件的时候,第二个if和第一个if之间可能已经隔了很长的时间,很多变量的值,可能已经发生了很大的变化,所以在执行第一个if的时候instance是null,到执行第二个if的时候,就不一定了,所以还要再对instance的值做一次判定,防止new出多个实例.


上述懒汉模式代码,如果考虑内存可见性问题,就还有bug

假设很多线程,都去进行getinstancce,这个时候,是否存在被优化的风险呢(只有第一次真正读了内存,后续都是读寄存器/cache)

另外,除了内存可见性问题,还有可能会出现指令重排序.

instance = new SingletonLazy();

可以拆分成三个步骤:

1.申请内存空间

2.调用构造方法,把这个内存空间初始化成一个合理的对象

3.把内存空间的地址赋给instance引用

正常情况下,是按照123这个顺序来执行的.

如果编译器为了优化,提高代码效率而调整了代码指令的执行顺序:123可能会变成132

(如果是单线程123和132没有本质区别),多线程的环境下,就有问题了!!

假设t1是按照132的顺序执行的.t1执行完13之后,执行2之前,被切出cpu t2来执行.

(当t1执行完3之后,t2看起来,此处的引用就非空了),此时此刻,t2就相当于直接返回了instance引用,并且可能会尝试使用引用其中的属性.

但是由于t1中的2操作还没执行完呢,t2拿到是非法的对象,还没构造完成的不完整的对象.

此时,可以用volatile解决这个问题!!!

volatile有两个功能:

1.解决内存可见性问题

2.禁止指令重排序.

所以,完整的懒汉模式的代码应如下:

class SingletonLazy {
    private volatile static SingletonLazy instance = null;

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

    private SingletonLazy() {}
}

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值