内容简介:在面试中被问到频率最高的设计模式是单例,因为它写起来很简单,而且了解单例模式的都知道,它有饿汉式、懒汉式、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
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Algorithms for Image Processing and Computer Vision
Parker, J. R. / 2010-12 / 687.00元
A cookbook of algorithms for common image processing applications Thanks to advances in computer hardware and software, algorithms have been developed that support sophisticated image processing with......一起来看看 《Algorithms for Image Processing and Computer Vision》 这本书的介绍吧!