Java单例模式实现,一次性学完整,面试加分项
单例模式是设计模式中使用最为普遍的一种模式。属于对象创建模式,它可以确保系统中一个类只产生一个实例。这样的行为能带来两大好处:
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销。
- 由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻gc压力,缩短gc停顿时间。
在实际应用中,很多时候有一些对象我们只需要一个,例如:线程池(threadpool)、缓存(cache)、注册表(registry)、日志对象等等,这个时候把它设计为单例模式是最好的选择。
1、单例模式6种实现方法
1)懒汉模式(线程不安全)
public class singleton01 { private static singleton01 instance; /** * 私有构造方法 */ private singleton01(){} public static singleton01 getinstance() { if(instance == null) { instance = new singleton01(); } return instance; } }
这种写法实现延迟加载,但线程不安全。禁止使用!
2)懒汉模式(线程安全)
public class singleton02 { private static singleton02 instance; /** * 私有构造方法 */ private singleton02(){} public static synchronized singleton02 getinstance() { if(instance == null) { instance = new singleton02(); } return instance; } }
这种写法实现延迟加载,且增加synchronized来保证线程安全,但效率太低。不建议使用
3)懒汉模式(双重校验锁)
public class singleton03 { private volatile static singleton03 instance; /** * 私有构造方法 */ private singleton03(){} public static singleton03 getinstance() { if(instance == null) { synchronized (singleton03.class) { if (instance == null) { instance = new singleton03(); } } } return instance; } }
使用到了volatile机制。这个是第二种方式的升级版,俗称双重检查锁定。既保证了效率,又保证了安全。
4)饿汉模式
public class singleton03 { private static singleton03 instance = new singleton03(); /** * 私有构造方法 */ private singleton03(){} public static synchronized singleton03 getinstance() { return instance; } }
这种基于类加载机制避免了多线程的同步问题,初始化的时候就给装载了。但却没了懒加载的效果。
这也是最简单的一种实现。
5)静态内部类
public class singleton04 { // 静态内部类 private static class singletonholder { private static final singleton04 instance = new singleton04(); } /** * 私有构造方法 */ private singleton04(){} public static singleton04 getinstance() { return singletonholder.instance; } }
这种方式当singleton04
类被加载时,其内部类并不会被加载,所以单例类instance
不会被初始化。
只有显式调用getinstance
方法时,才会加载singletonholder
,从而实例化instance
。
由于实例的建立是在类加载时完成,所以天生线程安全。因此兼备了懒加载和线程安全的特性。
6)枚举(号称最好)
public enum enumsingleton01 { instance; public void dosomething() { system.out.println("dosomething"); } }
模拟数据库链接:
public enum enumsingleton02 { instance; private dbconnection dbconnection = null; private enumsingleton02() { dbconnection = new dbconnection(); } public dbconnection getconnection() { return dbconnection; } }
这种方式是effective java
作者josh bloch
提倡的方式,它不仅能避免多线程同步问题,
而且还能防止反序列化重新创建新的对象。
2、为什么说枚举方法是最好的?
前5种方式实现单例都有如下3个特点:
- 构造方法私有化
- 实例化的变量引用私有化
- 获取实例的方法共有
首先,私有化构造器并不保险。因为它抵御不了反射攻击
,其次就是序列化重新创建新对象。下面来进行验证。
1) 反射验证
@test public void reflecttest() throws exception { singleton03 s = singleton03.getinstance(); // 拿到所有的构造函数,包括非public的 constructor<singleton03> constructor = singleton03.class.getdeclaredconstructor(); constructor.setaccessible(true); // 构造实例 singleton03 reflection = constructor.newinstance(); system.out.println(s); system.out.println(reflection); system.out.println(s == reflection); }
输出结果:
org.yd.singleton.singleton03@61e4705b org.yd.singleton.singleton03@50134894 false
再看看枚举类的测试
@test public void reflectenumtest() throws exception { enumsingleton01 s = enumsingleton01.instance; // 拿到所有的构造函数,包括非public的 constructor<enumsingleton01> constructor = enumsingleton01.class.getdeclaredconstructor(); constructor.setaccessible(true); // 构造实例 enumsingleton01 reflection = constructor.newinstance(); system.out.println(s); system.out.println(reflection); system.out.println(s == reflection); }
输出结果:
java.lang.nosuchmethodexception: org.yd.singleton.enumsingleton01.<init>() at java.lang.class.getconstructor0(class.java:3082) at java.lang.class.getdeclaredconstructor(class.java:2178) at org.yd.singleton.singletontest.reflectenumtest(singletontest.java:61) at sun.reflect.nativemethodaccessorimpl.invoke0(native method) at sun.reflect.nativemethodaccessorimpl.invoke(nativemethodaccessorimpl.java:62) at sun.reflect.delegatingmethodaccessorimpl.invoke(delegatingmethodaccessorimpl.java:43)
结论:通过反射,单例模式的私有构造方法也能构造出新对象。不安全。而枚举类直接抛异常,说明枚举类对反射是安全的。
2) 序列化验证
@test public void serializetest(){ singleton03 s = singleton03.getinstance(); string serialize = json.tojsonstring(s); singleton03 deserialize =json.parseobject(serialize,singleton03.class); system.out.println(s); system.out.println(deserialize); system.out.println(s == deserialize); }
输出结果:
org.yd.singleton.singleton03@387c703b org.yd.singleton.singleton03@75412c2f false
结论:序列化前后两个对象并不相等。所以序列化也是不安全的。
同样看看枚举类的测试
@test public void serializeenumtest(){ enumsingleton01 s = enumsingleton01.instance; string serialize = json.tojsonstring(s); enumsingleton01 deserialize =json.parseobject(serialize,enumsingleton01.class); system.out.println(s); system.out.println(deserialize); system.out.println(s == deserialize); }
输出结果:
instance instance true
结论:说明枚举类序列化安全。
综上,可以得出结论:枚举是实现单例模式的最佳实践。
- 反射安全
- 序列化/反序列化安全
- 写法简单
上一篇: 【硬核】23种设计模式娓娓道来,助你优雅的编写出漂亮代码!
下一篇: 原型模式(四)