设计模式-单例模式

111 阅读3分钟

这是我参与 8 月更文挑战的第 5 天,活动详情查看: 8月更文挑战

懒汉式

当使用到该对象时,再创建对象。

优点:节约空间

缺点:性能延迟,非线程安全,需要手动实现线程安全

双重锁 Double check lock

1.0版本

判断当前类变量是否为null,为null时代表还未创建,创建并返回

出现的问题多线程并发的情况下,有A线程创建了,但是B线程这时候不知道A已经创建,又创建了1个,导致懒汉式失效

private static LazyMan lazyMan;
​
public static LazyMan getInstance(){
   if (lazyMan==null){
       lazyMan = new LazyMan();
  }
   return lazyMan;
}

2.0版本双重检测锁模式

sync将同步机制,解决未创建实例的情况下多线程并发可能会创建多个实例的问题。

但是为什么要双重检测呢?

  • 如果单层检测,那么每个线程想要获得实例都需要走同步机制,十分耗时。在同步前面再加一层判断,当创建完之后,就无需走加锁步骤,节省时间
private static LazyMan lazyMan;
​
public static LazyMan getInstance2(){
   if (lazyMan==null){
       synchronized (LazyMan.class){
           if(lazyMan==null){
               lazyMan = new LazyMan();
          }
      }
  }
   return lazyMan;
}

3.0模式:给实例加上volatile修饰

volatile可以防止指令重排

创建实例的过程中可能会指令重排:1.分配内存空间 2.初始化对象 3.将这个对象指向空间

重排后:1.分配内存空间 2.将空间指向某个对象 3.这个对象等于初始化的对象

那么这时候如果 1.2步时判断 空间以被指向,所以不为空。但是事实上这个对象还没有初始化,就会返回null。

private volatile static LazyMan lazyMan;

静态内部类

缺点:无法传参

实现方式外部类加载时不会加载内部类,因此不会去初始化instance,不会占据空间。

调用getInstance() 方法被调用时,才回去第一次初始化instance

第一次调用getInstance()方法会导致虚拟机加载内部类。 确保线程安全,保证单例的唯一性,延迟单例的实例化

/* 当创建外部类的实例时,不会创建内部类。
       当调用getInstance方法时,才会生成单例。
   * */
​
   // 创建静态内部类
   private static class SingleTonHolder{
       // 实例化
       private static SingleTon instance = new SingleTon();
  }
​
   /* 当调用该方法时,才回去加载静态内部类。静态内部类加载时,立刻会创建1个外部类的实例(相当于饿汉式)*/
   private static SingleTon getInstance(){
       return SingleTonHolder.instance;
  }
​
public static void main(String[] args) {
       // 调用静态方法创建单例
       SingleTon instance = SingleTon.getInstance();
  }

实现原理:Lazy initialization holder class模式

类级内部类:内部类被static修饰, 方法。 对象级内部类:内部类没有被static修饰 ,方法。

类级内部类可以直接通过外部类创建,不存在依赖关系。 对象级内部类需要外部类的实例才能创建,有依赖关系

类级内部类相当于外部类的成员,只有第一次被使用时才会加载

JVM在某些情况下会自动加锁,就不需要自己手动sync:

1.静态初始化器(在静态字段上或 static{} 块中的初始化器)初始化数据。

2.访问final字段

3.创建线程之前创建对象

4.线程可以看见它将要处理的对象时

虚拟机再执行时会被加锁,同步。只会有一个线程可以执行方法,其他处于阻塞等待。执行方法后,其他线程唤醒就不会再进入clinit方法.

因此,在静态内部类的创建实例过程中时线程安全的。

枚举

可以防止反射破坏安全

public enum EnumSingle {
​
   USER("11","yzy");
​
   private String id;
​
   private String name;
   
   private EnumSingle(String id ,String name){
       this.id = id;
       this.name = name;
  }
​
   private String getId(){
       return id;
  }
​
   public static void main(String[] args) {
   // 获得单例对象
       EnumSingle user = EnumSingle.USER;
  }
}

饿汉式

加载类的时候,就直接创建对象。

优点:线程安全,第一次调用速度快。

缺点:无论是否用到,都要占据空间。