设计模式之单例模式
今天来说一下同样属于创建型模式的单例模式,相信这个模式普遍都清楚,因为平时在编码的时候都会进行相应的使用,我这边就当做日志记录一下。免得以后忘了还得去搜,我发现我记忆里非常差,很多东西很快就忘记了,年纪大了没办法。
一、定义
保证一个类仅有一个实例,并提供全局访问点。就是打死也不会生成第二个实例。一般用在工具类上,可以降低内存消耗
二、实例
单例模式有几种,分别是饿汉式、懒汉式、静态内部类单例以及枚举类单例。
(1)饿汉式
顾名思义,饿汉式就是整个项目在启动的时候进行创建。此时可以使用静态代码块。
public class HungrySingleton{ private final static HungrySingleton instance; static { instance = new HungrySingleton(); } public static HungrySingleton getInstance() { return instance; } }
此时外部还是可以通过new的方式创建一个实例化对象,只需要添加一个私有的构造方法,外部就无法通过new实例化对象。
private HungrySingleton() { }
但是,注意,此时依然是可以通过反射进行构造对象的,例如:
Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class; Constructor<HungrySingleton> constructor = hungrySingletonClass.getDeclaredConstructor(null); 1、打开私有方法的权限 constructor.setAccessible(true); HungrySingleton hungrySingleton = constructor.newInstance(); System.out.println(hungrySingleton == HungrySingleton.getInstance());
运行结果如下:
这个方法好像只能通过枚举类单例才能解决。
此时还是会有一个问题,当单例对象进行序列化之后,通过反序列化出来的结果是不一样的。
比如:
1、这个类需要实现Serializable才能进行序列化 HungrySingleton hungrySingleton = HungrySingleton.getInstance(); 2、序列化 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serial_single")); oos.writeObject(hungrySingleton); 3、反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("serial_single")); HungrySingleton unserialInstance = (HungrySingleton) ois.readObject(); 4、查看原有类与反序列化后生成的类是否相同 System.out.println(hungrySingleton == unserialInstance);
结果是false
此时需要在HungrySingleton 类中实现readResolve方法。
private Object readResolve(){ return instance;}
此时再看上述代码的运行结果
此时反序列化之后就是相同的了,这是为啥来,看一下源码:
点进ObjectInputStream 的readObject方法,然后进入Object obj = readObject0(false);这个方法。
此时会有一个判断读取对象类型的switch代码块,我们的是object类,所以进入readOrdinaryObject方法
而isInstantiable这个方法是只要实现了serializable就返回true
可以看到,这里会根据读取的class去实例化一个新的对象。
但是,下面还有一段代码,看这个desc.hasReadResolveMethod()方法,这个就是判断有没有实现readResolve方法的
boolean hasReadResolveMethod() { requireInitialized(); return (readResolveMethod != null); }
而这个readResolveMethod字段是一个Method类型的,其在ObjectStreamClass构造方法中。
所以只要实现了readResolve方法就可以反序列化出一样的对象。
(2)懒汉式
懒汉式的意思是只有需要用到这个类的时候才会进行创建。
public class LazySingleton { private static LazySingleton instance = null; private LazySingleton(){} public static LazySingleton getInstance() { 1、if (instance == null) { 2、instance = new LazySingleton(); } return instance; } }
这种的话就会有线程不安全的情况,比如线程A跑到1处,线程B跑到2处还没进行实例化的时候,线程A依然会进入if代码块进行实例化,此时会产生两个实例化对象。
此时可以进行加锁:用synchronized 关键字
public synchronized static LazySingleton getInstance() { if (instance == null) { instance = new LazySingleton(); } return instance; }
这样的话会影响性能,因为把整个方法都锁定了,当线程A获取锁之后,其他线程就只能等待线程A把锁释放。
这个时候可以只对实例化的代码块加锁,这就是双层锁定了,例如:
public static LazySingleton getDoubleCheckInstance() { 1、if (instance == null) { synchronized (LazySingleton.class) { if (instance == null) { 2、instance = new LazySingleton(); } } } return instance; }
此时又会有一个问题,在instance = new LazySingleton();这行代码会发生指令重排的现象,创建实例化对象的过程是有三步,
1、分配内存
2、初始化对象
3、将堆内对象的地址赋值给instance
第2和第3是没有前后关系的,可以先执行第3步。
这个就会发生这种情况,线程A获取锁进来了,在代码2处实例化对象时先将地址赋值给instance,此时instance就不是空对象了,其他线程刚好进入代码1处,instance不为null,此时就会直接返回。
这个情况的解决方法是volatile关键字,其可以禁止指令重排。只需在单例对象加上此关键字。
private static volatile LazySingleton instance = null;
(3)静态内部类单例模式
public class StaticInnerClassSingleton { private static class InnerClass{ private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton(); } public static StaticInnerClassSingleton getInstance(){ return InnerClass.staticInnerClassSingleton; } }
(4)枚举类单例
public enum EnumSingleton { INSTANCE; private String data; EnumSingleton() { this.data = new String("abc"); } public String getInstance () { return data; } }
枚举类是无法通过反射去实例化对象的
Class<EnumSingleton> enumSingletonClass = EnumSingleton.class; Constructor<EnumSingleton> constructor = enumSingletonClass.getDeclaredConstructor(null); constructor.setAccessible(true); EnumSingleton hungrySingleton = constructor.newInstance();
上述代码运行之后会抛异常:
而且枚举类天然是序列化和反序列化是一致的
EnumSingleton enumSingleton = EnumSingleton.INSTANCE; ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("serial_single")); oos.writeObject(enumSingleton); ObjectInputStream ois = new ObjectInputStream(new FileInputStream("serial_single")); EnumSingleton unserialInstance = (EnumSingleton) ois.readObject(); System.out.println(enumSingleton == unserialInstance);
上述代码运行结果是:
所以综合上述所讲,枚举类单例是最优的解决方案。
=======================================================
我是Liusy,一个喜欢健身的程序员。
欢迎关注微信公众号【Liusy01】,一起交流Java技术及健身,获取更多干货,领取Java进阶干货,一起成为Java大神。
来都来了,关注一波再溜呗。