内容简介:在面试中被问到频率最高的设计模式是单例,因为它写起来很简单,而且了解单例模式的都知道,它有饿汉式、懒汉式、DCL(双重锁判断)、静态内部类以及枚举等多种写法。但说实话,在实际应用中,单例用到的并不是很多。但作为设计模式的基本模式之一,我们也有必要了解单例是否满足需求,例如线程是否安全,是否延迟加载,反射是否安全,序列化是否安全,这是本文重点关注的问题。单例模式就是在应用的整个生命周期中只存在一个实例。它有很多好处,避免实例对象的重复创建,减少实例对象的重复创建,减少系统开销。例如spring容器中管理的Be
在面试中被问到频率最高的 设计模式 是单例,因为它写起来很简单,而且了解单例模式的都知道,它有饿汉式、懒汉式、DCL(双重锁判断)、静态内部类以及枚举等多种写法。但说实话,在实际应用中,单例用到的并不是很多。但作为设计模式的基本模式之一,我们也有必要了解单例是否满足需求,例如线程是否安全,是否延迟加载,反射是否安全,序列化是否安全,这是本文重点关注的问题。
单例模式就是在应用的整个生命周期中只存在一个实例。它有很多好处,避免实例对象的重复创建,减少实例对象的重复创建,减少系统开销。例如spring容器中管理的Bean默认就是单例的。
五种单例模式
饿汉式
写法
public class HungrySingleton implements Serializable{ private static HungrySingleton singleton = new HungrySingleton(); private HungrySingleton() { } public static HungrySingleton getInstance() { return singleton; } } 复制代码
之所以implements Serializable(下同),是为了后面测试序列化是否安全的需要,一般情况不用加。
特性
饿汉式在类加载时期就已经初始化实例,而我们知道类加载是线程安全的,所以饿汉式是线程安全的。很明显,它不是延迟加载的,这也是饿汉式的缺点。通过下面的测试方法1,饿汉式不是反射安全的,因为通过反射构造方法产生了两个实例。通过测试方法2,饿汉式也不是序列化安全的。
测试方法1:
public static void main(String [] args) { //测试饿汉式反射是否安全 reflectTest(); } private static void reflectTest() { HungrySingleton singleton1 = HungrySingleton.getInstance(); HungrySingleton singleton2 = null; try { Class<HungrySingleton> clazz = HungrySingleton.class; Constructor<HungrySingleton> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); singleton2 = constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println(singleton1.hashCode()); System.out.println(singleton2.hashCode()); } 复制代码
运行结果:
测试方法2:
public static void main(String [] args) { //测试饿汉式序列化是否安全 serializableTest(); } private static void serializableTest() { HungrySingleton singleton1 = HungrySingleton.getInstance(); HungrySingleton singleton2 = null; try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File("C:\\1.txt"))); outputStream.writeObject(singleton1); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("C:\\1.txt"))); singleton2 = (HungrySingleton) inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } System.out.println(singleton1.hashCode()); System.out.println(singleton2.hashCode()); } 复制代码
运行结果:
懒汉式
写法
public class LazySingletonThreadNotSafe implements Serializable{ private static LazySingletonThreadNotSafe singleton = null; private LazySingletonThreadNotSafe() { } public static LazySingletonThreadNotSafe getSingleton() { if (singleton == null) { singleton = new LazySingletonThreadNotSafe(); } return singleton; } } 复制代码
特性
懒汉式在饿汉式的基础上进行了改造,将实例的初始化从类加载过程移到getInstance()方法真正调用时进行。所以具备了延迟加载,但失去了线程安全性。下面的DCL在此基础上增加了线程安全。从测试方法1和2可知,懒汉式反射不安全,序列化也不安全。 测试方法1:
public static void main(String [] args) { //测试懒汉式反射是否安全 reflectTest(); } private static void reflectTest() { LazySingletonThreadNotSafe singleton1 = LazySingletonThreadNotSafe.getSingleton(); LazySingletonThreadNotSafe singleton2 = null; try { Class<LazySingletonThreadNotSafe> clazz = LazySingletonThreadNotSafe.class; Constructor<LazySingletonThreadNotSafe> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); singleton2 = constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println(singleton1.hashCode()); System.out.println(singleton2.hashCode()); } 复制代码
运行结果:
测试方法2:
public static void main(String [] args) { //测试懒汉式序列化是否安全 serializableTest(); } private static void serializableTest() { LazySingletonThreadNotSafe singleton1 = LazySingletonThreadNotSafe.getSingleton(); LazySingletonThreadNotSafe singleton2 = null; try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File("C:\\1.txt"))); outputStream.writeObject(singleton1); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("C:\\1.txt"))); singleton2 = (LazySingletonThreadNotSafe) inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } System.out.println(singleton1.hashCode()); System.out.println(singleton2.hashCode()); } 复制代码
运行结果:
DCL(双重锁判断Double Check Lock)
写法
public class LazySingletonThreadSafe implements Serializable{ private volatile static LazySingletonThreadSafe singleton =null; private LazySingletonThreadSafe() { } public static LazySingletonThreadSafe getSingleton() { if (singleton == null) { //1 synchronized (LazySingletonThreadSafe.class) { //2 if (singleton == null) { //3 singleton = new LazySingletonThreadSafe(); //4 } } } return singleton; } } 复制代码
特性
DCL是在懒汉式基础上的改进,跟懒汉式唯一不同的是DCL是线程安全的。你可能会问,有了synchronized保证线程安全,为啥还要加volatile修饰?因为DCL本身存在一个致命缺陷,就是重 排序 导致的多线程访问可能获得一个未初始化的对象。
我们知道singleton = new LazySingletonThreadSafe();这行代码在JVM看来有这么三步:
1、为对象分配存储空间
2、初始化对象
3、将singleton引用指向第一步中分配的内存地址
第2步和第3步可能存在重排序。假设线程A按2、3步颠倒的顺序执行代码(发生了重排序),先执行了第3步,此时singleton引用已经指向了第一步中分配的内存地址,当线程B执行getSingleton()方法时,发现singleton != null,就执行获得了还没有初始化的singleton,这样就出问题了。我们知道volatile的性质是保证多线程环境下变量的可见性以及禁止指令重排序,所以要加volatile。
静态内部类
写法
public class StaticInnerSingleton implements Serializable{
private StaticInnerSingleton() { } /** * 静态内部类,它和饿汉式一样,基于类加载机制的线程安全,又做到延迟加载。 * SingletonHolder是一个内部类,当外部类StaticInnerSingleton被加载的时候不会被加载, * 调用getSingleton方法的时候才会被加载。 */ private static class SingletonHolder { private static final StaticInnerSingleton singleton = new StaticInnerSingleton(); } public static StaticInnerSingleton getSingleton() { return SingletonHolder.singleton; } 复制代码
}
特性
静态内部类和饿汉式一样是线程安全的,同时又做到了延迟加载。但是反射不安全,序列化也不安全。
测试方法1:
public static void main(String [] args) { //测试静态内部类反射是否安全 reflectTest(); } private static void reflectTest() { StaticInnerSingleton singleton1 = StaticInnerSingleton.getSingleton(); StaticInnerSingleton singleton2 = null; try { Class<StaticInnerSingleton> clazz = StaticInnerSingleton.class; Constructor<StaticInnerSingleton> constructor = clazz.getDeclaredConstructor(); constructor.setAccessible(true); singleton2 = constructor.newInstance(); } catch (Exception e) { e.printStackTrace(); } System.out.println(singleton1.hashCode()); System.out.println(singleton2.hashCode()); } 复制代码
运行结果:
测试方法2:
public static void main(String [] args) { //测试静态内部类序列化是否安全 serializableTest(); } private static void serializableTest() { StaticInnerSingleton singleton1 = StaticInnerSingleton.getSingleton(); StaticInnerSingleton singleton2 = null; try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream(new File("C:\\1.txt"))); outputStream.writeObject(singleton1); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(new File("C:\\1.txt"))); singleton2 = (StaticInnerSingleton) inputStream.readObject(); } catch (Exception e) { e.printStackTrace(); } System.out.println(singleton1.hashCode()); System.out.println(singleton2.hashCode()); } 复制代码
运行结果:
枚举
写法(简单)
public enum EnumInstance implements Serializable{ INSTANCE; } 复制代码
特性
用 java 反编译 工具 看看Enum的源码,跟饿汉式一样,是在类加载时就初始化了,是线程安全的,所以并不是延迟加载的。
public final class EnumSingleton extends Enum { public static EnumSingleton[] values() { return (EnumSingleton[])$VALUES.clone(); } public static EnumSingleton valueOf(String s) { return (EnumSingleton)Enum.valueOf(test/singleton/EnumSingleton, s); } private EnumSingleton(String s, int i) { super(s, i); } public static final EnumSingleton INSTANCE; private static final EnumSingleton $VALUES[]; static { INSTANCE = new EnumSingleton("INSTANCE", 0); $VALUES = (new EnumSingleton[] { INSTANCE }); } } 复制代码
测试方法:
public static void main(String [] args) { //测试枚举反射是否安全 reflectTest(); } private static void reflectTest() { EnumInstance singleton1 = EnumInstance.INSTANCE; EnumInstance singleton2 = null; try { Class<EnumInstance> clazz = EnumInstance.class; Constructor<EnumInstance> constructor = clazz.getDeclaredConstructor(String.class,int.class); constructor.setAccessible(true); singleton2 = constructor.newInstance("test",1); } catch (Exception e) { e.printStackTrace(); } } 复制代码
运行结果:
直接不让反射了,说明枚举是反射安全的。在 constructor.newInstance()源码中,有这么几行,是枚举类型直接抛异常了。最后枚举单例也是序列化安全的,可以自己测试一下。
总结
通过以上测试,了解了五种单例模式各有优缺点,没有说哪种单例模式最好,只有满足需求的才是最合适的。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 如何复用一套代码满足多样化的需求?
- 专访网易云陈谔:用微服务体系满足数字化转型需求
- 直击|联想与中科院共建HPC平台 满足5年科研需求
- 360度测试:KAFKA会丢数据么?其高可用是否满足需求?
- 社区问答系统精准匹配信息和人,满足你对获取知识的迫切需求
- C++无法满足超算编程需求,斯坦福研发专用语言Regent
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Thirty-three Miniatures
Jiří Matoušek / American Mathematical Socity / 2010-6-18 / USD 24.60
This volume contains a collection of clever mathematical applications of linear algebra, mainly in combinatorics, geometry, and algorithms. Each chapter covers a single main result with motivation and......一起来看看 《Thirty-three Miniatures》 这本书的介绍吧!