第三梦 单例模式
Posted yrml
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了第三梦 单例模式相关的知识,希望对你有一定的参考价值。
初识单例
单例模式,算是我们代码中经常遇见的设计模式之一了。当然我们也上手很快,但是其中的坑也不少,不好好研究一下,这些坑还真不好跳过去。单例简单分分别为懒汉模式、饿汉模式,那我们就从懒汉模式开始吧。
懒汉模式(线程非安全)
这里定义一个私有的全局变量singletonPattern,然后通过一个公有的静态方法对singletonPattern进行判空,如果为空,就new一个类对象出来,然后返回该对象。该种方式可以实现类对象在使用的时候才创建,也就是延时加载。
1 public class SingletonPattern { 2 3 private static SingletonPattern singletonPattern = null; 4 5 private SingletonPattern() { 6 } 7 8 public static SingletonPattern getInstance(){ 9 // if这里存在竞态条件 10 if(singletonPattern == null){ 11 singletonPattern = new SingletonPattern(); 12 } 13 return singletonPattern; 14 } 15 }
懒汉模式(线程安全、低效)
一种比较简单的方式,是同步获取实例化的方法getInstance(),也就是加上synchronized关键字。当然这种方式是非常低效的(jdk后面的版本对synchronized关键字段的底层代码做了很强的优化,所以也不是不可以考虑),具体如下:
1 public class SingletonPattern { 2 3 private static SingletonPattern singletonPattern = null; 4 5 private SingletonPattern() { 6 } 7 8 public static synchronized SingletonPattern getInstance(){ 9 if(singletonPattern == null){ 10 singletonPattern = new SingletonPattern(); 11 } 12 return singletonPattern; 13 } 14 }
懒汉模式(线程安全、高效)
1、双重锁校验DCL(半成品,问题代码,面试考点),注意看下面罗列的四步。
1 public class SingletonPatternDcl { 2 3 private static SingletonPatternDcl singletonPatternDcl = null; 4 5 private SingletonPatternDcl() { 6 } 7 8 public static SingletonPatternDcl getInstance(){ 9 if(singletonPatternDcl == null){ //1、在实例化的情况下,不需要执行加锁动作,性能提高 10 synchronized (SingletonPatternDcl.class){ //2、对类上锁,多个线程的情况下,只有一个线程能够创建对象 11 if(singletonPatternDcl == null){ //3、实例化对象为空的情况下创建对象 12 singletonPatternDcl = new SingletonPatternDcl(); //4、创建对象 13 } 14 } 15 } 16 return singletonPatternDcl; 17 } 18 }
2、完美的DCL。上面的DCl看起来是非常完美的,所有的逻辑都考虑到了,但是上面的第四步singletonPatternDcl = new SingletonPatternDcl()创建对象的过程其实并非是一个原子操作,这就导致了问题的产生。我们来分析一下第四步在JVM中具体做了哪些事情:
- a、给singletonPatternDcl分配内存空间
- b、调用SingletonPatternDcl的构造函数来初始化该成员变量
- c、将singletonPatternDcl对象指向a步骤分配的内存空间(这一步执行完之后,singletonPatternDcl就为非null了)
而在JVM的即时编译器中存在指令重排序的优化,如果c步骤在b步骤之前执行的话:b执行了,singletonPatternDcl不为空了,第二个线程来了,发现singletonPatternDcl已经不为null了,然后直接返回。但是其实这个时候singletonPatternDcl只是一个内存地址,根本还没有初始化,程序就理所当然的报错了。解决的方法很简单,基于volatile解决方案,如下所示:
private static volatile SingletonPatternDcl singletonPatternDcl = null;
volatile的特性禁止指令重排序,保证了上述a、b、c一定会按着abc的顺序执行,也就避免了上述产生问题的场景。
饿汉模式(天然的线程安全)
利用类加载的机制,我们可以在类一开始加载的时候就初始化一个实例对象。缺点是无法实现懒加载,并且在某些需要使用动态参数的情况下无法使用。
1 public class SingletonPatternSafe { 2 3 private static SingletonPatternSafe singletonPatternSafe = new SingletonPatternSafe(); 4 5 private SingletonPatternSafe() { 6 } 7 8 public SingletonPatternSafe getInstance() { 9 return singletonPatternSafe; 10 } 11 }
这里加上final也是可以的
private static final SingletonPatternSafe singletonPatternSafe = new SingletonPatternSafe();
静态内部类(天然的线程安全)
这种方式的单例实现,也是基于JVM本身机制保证了线程安全。其内部类Holder只有getInstance()方法可以访问。读取的实例的时候也不需要进行同步,没有性能的损失。
1 public class SingletonPatternHolder { 2 3 private static class Holder { 4 private static final SingletonPatternHolder INSTANCE = new SingletonPatternHolder(); 5 } 6 7 private SingletonPatternHolder() { 8 } 9 10 public static SingletonPatternHolder getInstance(){ 11 return Holder.INSTANCE; //懒汉式的,只有访问getInstance()方法的时候才实例化 12 } 13 14 }
枚举方式(绝对的线程安全)
枚举实现单例模式有三个特性:自由序列化、线程安全、保证单例。
- enum的实现是通过继承了Enum类来实现的,enum结构不能作为子类来继承其他类,但是可以用来实现接口类;
- 由于enum内部的实现方式其实是final类型的,所以enum类不可以被继承;
- enum有且仅有private构造器,防止外部的额外构造,这恰好和单例模式相符合;
- 其内部也是枚举量未被初始化,之后会在静态代码中进行初始化,这就非常类似饿汉模式;
- 对于序列化和反序列化,因为每一个枚举类型和枚举变量在JVM中都是唯一的,所以Java在序列化和反序列化枚举时做了特殊规定,枚举的writeObject、readObject、readReplace和readResolve等方式是被编译器禁止的,因此不存在实现序列化接口之后调用readObject会重新创建的心得对象从而破坏单例的问题。
基于上述描述,我们发现enum的方式来构造单例模式,代码实现起来非常的简单、自由序列化。并且也是线程的安全,相比起来应该更优选择
1 public enum SingletonPatternEnum { 2 3 /** 4 * 实例化对象 5 */ 6 INATANCE 7 }
代码实例
我的代码放在GitHub,小伙伴可以作为一个参考、
参考博文
SingletonPattern、我们平常用到的一个设计模式,有必要深入学习,掌握精髓,在实战中灵活运用。感谢前辈们的分享做为引路人。-------书山有路、人儿需行<<
以上是关于第三梦 单例模式的主要内容,如果未能解决你的问题,请参考以下文章