设计模式06 - 设计模式 - 单例设计模式(高频-创建型)

一、定义

  单例设计模式(Singleton Design Pattern)理解起来非常简单。一个类只允许创建一个对象(或者实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。因此,不允许new对象出来,构造函数肯定私有。

只确保一个类再任何情况下都绝对只有一个实例,并提供一个全局访问点

二、实现方式

2.1 饿汉式 - 安全

  饿汉式:项目启动,就初始化,不是使用时才初始化,非常安全

 1 public final class SingleObject {
 2     // 在定义实例对象的时候直接初始化
 3     private static SingleObject instance = new SingleObject();
 4  5     // 私有构造函数,不允许外部new
 6     private SingleObject() {}
 7  8     public static SingleObject getInstance() {
 9         return instance;
10     }
11 }

 

分析:

  SingleObject类是使用final修饰的,不允许被继承。在饿汉式的单例模式下,SingleObject类对象instance对象在类加载的初始化阶段就会直接完成创建,因此该方法在多线程的情况下也能保证同步,因为不可能被实例化两次,不存在线程不安全的情况。 适用场景:如果一个类的成员属性比较少,且占用的内存资源不多,饿汉式的性能是比较高的;

2.2 懒汉式 - 不安全

 1 public final class SingleObject {
 2     //懒加载
 3     private static SingleObject instance = null;
 4  5     private SingleObject() {}
 6     //使用时在加载
 7     public static SingleObject getInstance() {
 8         if (instance == null) {
 9             instance = new SingleObject();
10         }
11         return instance;
12     }
13 }

安全性能分析:

  如果两个线程A和B,A调用getInstance(),执行instance = new Singletion()需要一定时间;在这段时间中,B也调用了getInstance(),也要去执行instance = new SingleObject(),这样instance就被实例化了两次,线程不同步了。

有人说:可以对整个方法加synchronized解决,但是性能太低,同时只可以有一个线程使用;不行;

2.3 懒汉升级 Double-Check - 安全

 1 public final class SingleObject {
 2  3     private byte[] data = new byte[1024];
 4  5     // 加volatile关键字保证有序性,防止指令重排序,
 6     private volatile static SingleObject instance = null;
 7     
 8     private SingleObject() {
 9        //有可能做很多事情,比如做socket连接,可能就指令重排了
10     }
11     public static SingleObject getInstance() {
12         
13         if (instance == null) { //判断是否已经实例化,如果实例化,就直接获取了
14             synchronized (SingleObject.class) {
15                 if (instance == null) { //有可能有A、B两个线程同时经过了第一个if,假设A抢到了资源,那么防止B在进入的时候再次创建
16                     //防止指令重拍
17                     instance = new SingleObject();
18                 }
19             }
20         }
21         return instance;
22     }
23 }

分析:

  如果不加volatile,假设线程A第一个调用getInstance()方法,此时instance == null,因此A获取到同步资源,对instance进行加载。由于指令会被重排序,在SingleObject的构造函数中,有可能instance最先被实例化,而socket连接后被实例化。在socket被实例化的过程中,又有一个线程B调用了getInstance()方法,它发现instance != null,则获取到了instance,然后又去使用socket,就会产生空指针异常。

这种方式安全,但是不常用;

2.4 Holder方式 - 内部类方式 - 推荐

 1 public final class SingleObject {
 2  3     private byte[] data = new byte[1024];
 4  5     private SingleObject() {}
 6  7     // 在静态内部类中持有SingleObject的实例,并且可被直接初始化
 8     private static class Holder {
 9         private static SingleObject instance = new SingleObject();
10     }
11 12     // 调用getInstance()方法,实际上是获得Holder的instance静态属性
13     public static SingleObject getInstance() {
14         return Holder.instance;
15     }
16 }

  Holder方式完全借助了类加载的特性。在SingleObject类中并没有instance的静态成员变量,只有在其静态内部类Holder中有。因此,在SingleObject类初始化时,并不会创建Singleton的实例。只有在getInstance()方法第一次被调用时,Singleton的实例instance才会被创建,由此实现了懒加载。另外,这个创建过程是在Java编译时期收集至< clinit>()方法(JVM的一个内部方法,在类加载的初始化阶段起作用)中的。这个方法是同步方法,保证内存的可见性、JVM指令的原子性和有序性。 因此Holder方式可以保证线程安全、高性能和懒加载,它也是单例模式中最好的设计之一,使用非常广泛。

2.5 枚举方式 - 推荐

  在很多优秀开源代码中经常可以看到枚举方式的例子,因为优雅

 1 public enum SingleObject {
 2     INSTANCE;
 3  4     private static String data = "";
 5  6     SingleObject() {
 7         System.out.println("INSTANCE will br initialized immediately");
 8     }
 9 10     public static void method() {
11         // 调用该方法将会主动使用SingleObject, INSTANCE将会被初始化,绕过了getInstance()去调用,因此不能懒加载,我们希望调用getInstance()方法获取单例;
12         data = "123";
13     }
14     //调用该方法得到实例
15     public static SingleObject getInstance() {
16         return INSTANCE;
17     }
18 19 }

  枚举方式不能被继承,只能实例化一次,同样也是线程安全,且高效的。但是枚举方式不能懒加载,第一次调用Singleton枚举时,instance就会被实例化。

2.6 最强王者 枚举 + holder - 推荐

 1 public class SingleObject {
 2  3     private byte[] data = new byte[1024];
 4  5     private SingleObject() {}
 6  7     private enum EnumHolder {
 8         INSTANCE;
 9 10         private SingleObject instance;
11 12         EnumHolder() {
13             this.instance = new SingleObject();
14         }
15 16         private SingleObject getSingleObject() {
17             return instance;
18         }
19     }
20 21     public static SingleObject getInstance() {
22         return EnumHolder.INSTANCE.getSingleObject();
23     }
24 }

各个方式总结

线程安全高性能高性能懒加载补充
饿汉式 ×  
懒汉式 ×  
Double-Check+Volatile 加Volatile防止控制在异常
Holder方式  
枚举方式 × 可以添加Holder方式实现懒加载
枚举方式+Holder方式 最强王者

三、使用场景 - 待补充

  单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多(即:重量级对象),但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、session工厂等),当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new。

posted @ 2022-07-24 15:49  云执  阅读(20)  评论(0)    收藏  举报