单例模式

单例,就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。它的出现是为了节省资源。在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()区别:*********

getDeclaredConstructor(Class<?>... parameterTypes) 
这个方法会返回制定参数类型的所有构造器,包括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()区别?

金麟岂是池中物,一遇风云便化龙!
原文地址:https://www.cnblogs.com/ABKing/p/12227234.html