单例,就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。它的出现是为了节省资源。在Spring中,每个Bean默认就是单例的。在Java,一般常用在工具类的实现或创建对象需要消耗资源。
单例模式:
- 饿汉式(线程安全,调用效率高,但是,不能延时加载)
- 懒汉式(线程安全,调用效率不高,但是,可以延时加载)
- 双重检测锁式(由于JVM底层内部模型的问题,偶尔会出问题,不建议使用)
- 静态内部类模式(线程安全,调用效率高,可以延时加载)
- 枚举式(线程安全,调用效率高,但是,不能延时加载。并且可以天然地防止反射和反序列化漏洞!)
如何选用:
----单例对象 占用资源少 不需要 延时加载
- 枚举式 优于 饿汉式
----单例对象 占用资源大 需要延时加载
- 静态内部类 优于 懒汉式
饿汉模式
线程安全,比较常用,效率高,但容易产生垃圾,因为一开始就初始化,不能延时加载
1 package top.bigking.singleton; 2 3 /** 4 * @Author ABKing 5 * @Date 2020/1/31 下午5:50 6 * 单例模式之 饿汉式 7 **/ 8 public class SingletonDemo1 { 9 private static SingletonDemo1 instance = new SingletonDemo1();//类初始化时,立即加载 10 private SingletonDemo1(){}; 11 public static SingletonDemo1 getInstance(){ 12 return instance; 13 } 14 }
懒汉模式
线程不安全,延迟初始化,效率低偏低,严格意义上不是不是单例模式,可以延时加载
1 package top.bigking.singleton; 2 3 /** 4 * @Author ABKing 5 * @Date 2020/1/31 下午5:50 6 * 单例模式之 懒汉式 7 **/ 8 public class SingletonDemo2 { 9 private static SingletonDemo2 instance; 10 private SingletonDemo2(){}; 11 public static synchronized SingletonDemo2 getInstance(){ 12 if(instance == null){ 13 instance = new SingletonDemo2(); 14 } 15 return instance; 16 } 17 }
双重检测锁
1 package top.bigking.singleton; 2 3 /** 4 * @Author ABKing 5 * @Date 2020/2/3 下午6:48 6 * 单例模式之 双重检测锁 7 **/ 8 public class SingletonDemo3 { 9 private static SingletonDemo3 instance; 10 private SingletonDemo3(){}; 11 public static SingletonDemo3 getInstance(){ 12 if(instance == null){ 13 SingletonDemo3 sc; 14 synchronized (SingletonDemo3.class){ 15 sc = instance; 16 } 17 if(sc == null){ 18 synchronized (SingletonDemo3.class){ 19 if(sc == null){ 20 sc = new SingletonDemo3(); 21 } 22 } 23 instance = sc; 24 } 25 } 26 return instance; 27 } 28 }
静态内部类
1 package top.bigking.singleton; 2 3 /** 4 * @Author ABKing 5 * @Date 2020/2/3 下午8:32 6 * 单例模式 之 静态内部类 7 **/ 8 public class SingletonDemo4 { 9 //懒加载,而且类的加载是天然的线程安全的,当调用getInstance()方法时,才会加载 静态内部类 10 private static class SingletonClassInstance { 11 private static final SingletonDemo4 instance = new SingletonDemo4(); 12 } 13 //直接调用,不需要synchronized同步等待,高效并发 14 public static SingletonDemo4 getInstance(){ 15 return SingletonClassInstance.instance; 16 } 17 private SingletonDemo4(){}; 18 19 }
枚举式
1 package top.bigking.singleton; 2 3 /** 4 * @Author ABKing 5 * @Date 2020/2/4 下午5:16 6 **/ 7 public enum SingletonDemo5 { 8 //枚举元素,天然的 单例对象 9 INSTANCE; 10 11 //添加自己需要的一些操作 12 public void SingletonDemo5Operation(){ 13 14 } 15 }
使用反射 破解单例模式
1 @Test 2 public void testReflection() throws Exception { 3 Class<SingletonDemo1> clazz = (Class<SingletonDemo1>) Class.forName("top.bigking.singleton.SingletonDemo1"); 4 Constructor<SingletonDemo1> constructor = clazz.getDeclaredConstructor(null); 5 constructor.setAccessible(true); 6 SingletonDemo1 instance1 = constructor.newInstance(); 7 SingletonDemo1 instance2 = constructor.newInstance(); 8 System.out.println(instance1); 9 System.out.println(instance2); 10 }
运行结果:
1 top.bigking.singleton.SingletonDemo1@579bb367
2 top.bigking.singleton.SingletonDemo1@1de0aca6
可以看到,两个对象的内存地址不一致。我们成功破解了单例模式
********* getConstructor()和getDeclaredConstructor()区别:*********
这个方法会返回制定参数类型的所有构造器,包括public的和非public的,当然也包括private的。
getDeclaredConstructors()的返回结果就没有参数类型的过滤了。
再来看getConstructor(Class<?>... parameterTypes)
这个方法返回的是上面那个方法返回结果的子集,只返回制定参数类型访问权限是public的构造器。
getConstructors()的返回结果同样也没有参数类型的过滤。
接下来,我们寻求防止被破解的单例模式的写法。
在类的私有构造器中判断是否已经创建对象,如果在已经创建的情况下还调用构造器,则抛出异常
1 package top.bigking.singleton; 2 3 /** 4 * @Author ABKing 5 * @Date 2020/1/31 下午5:50 6 * 单例模式之 饿汉式 7 **/ 8 public class SingletonDemo1 { 9 private static SingletonDemo1 instance = new SingletonDemo1();//类初始化时,立即加载 10 private SingletonDemo1(){ 11 if(instance != null){ 12 throw new RuntimeException(); 13 } 14 }; 15 public static SingletonDemo1 getInstance(){ 16 return instance; 17 } 18 }
显然,通过上述方法,在私有构造器中进行判断,就能防止通过反射破坏单例模式
可是!通过反序列化仍然可以破坏单例模式
1 //利用反序列化 破解 单例模式 2 @Test 3 public void testUnSerialize() throws IOException, ClassNotFoundException { 4 SingletonDemo1 instance = SingletonDemo1.getInstance(); 5 System.out.println(instance); 6 7 //序列化 8 FileOutputStream fos = new FileOutputStream("/home/king/a.txt"); 9 ObjectOutputStream oos = new ObjectOutputStream(fos); 10 oos.writeObject(instance); 11 oos.close(); 12 fos.close(); 13 14 //反序列化 15 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("/home/king/a.txt")); 16 SingletonDemo1 s = (SingletonDemo1) ois.readObject(); 17 System.out.println(s); 18 }
运行结果如下
1 top.bigking.singleton.SingletonDemo1@579bb367
2 top.bigking.singleton.SingletonDemo1@1c655221
显然,内存地址不一致,即 破坏了单例模式!
那么如何防止反序列化破坏单例模式呢?
通过在类中添加readResolve()方法即可:
1 package top.bigking.singleton; 2 3 import java.io.Serializable; 4 5 /** 6 * @Author ABKing 7 * @Date 2020/1/31 下午5:50 8 * 单例模式之 饿汉式 9 **/ 10 public class SingletonDemo1 implements Serializable { 11 private static SingletonDemo1 instance = new SingletonDemo1();//类初始化时,立即加载 12 private SingletonDemo1(){ 13 if(instance != null){ 14 throw new RuntimeException(); 15 } 16 }; 17 public static SingletonDemo1 getInstance(){ 18 return instance; 19 } 20 21 //反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要创建新的对象 22 public Object readResolve() { 23 return instance; 24 } 25 }
反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要创建新的对象
运行结果如下:
1 top.bigking.singleton.SingletonDemo1@579bb367
2 top.bigking.singleton.SingletonDemo1@579bb367
显然,内存地址一致,是同一个对象,防止了反序列化破坏单例模式
使用多线程测试各种单例模式的运行速度
1 //多线程测试单例模式的运行速度 2 @Test 3 public void testSpeed() throws InterruptedException { 4 long start = System.currentTimeMillis(); 5 int threadNum = 10; 6 final CountDownLatch countDownLatch = new CountDownLatch(threadNum); 7 for (int i = 0; i < threadNum; i++) { 8 new Thread(new Runnable() { 9 @Override 10 public void run() { 11 for (int j = 0; j < 1000000; j++) { 12 Object o = SingletonDemo2.getInstance(); 13 //Object o = SingletonDemo5.INSTANCE; 14 } 15 countDownLatch.countDown(); 16 } 17 }).start(); 18 } 19 countDownLatch.await(); 20 long end = System.currentTimeMillis(); 21 System.out.println("总共耗时:" + (end - start)); 22 }
参考文献:
https://www.bilibili.com/video/av46777702?p=288 尚学堂java300集
https://blog.csdn.net/u012306714/article/details/64918737 如何通过反射来创建对象?getConstructor()和getDeclaredConstructor()区别?