别大意,人人都懂的单例模式,你能写出几种?

首先,什么是单例模式?

通俗来说就是,单例模式就是在程序运行过程中,一直只能存在一个实例

那么单例模式到底具备哪些特征呢?
1. 由这个类自己创建自己的实例,且最多只能创建一个
2. 构造方法私有化,不能在其它类里使用
3. 给外界提供一个获取实例的静态方法


单例模式的几种常见写法

1. 饿汉模式

public class P2 {
 
   private static P2 p2 = new P2();
 
   private P2() {}
 
   public static P2 getP2() {
      return p2;
   }
   
}

这大概是最简单的一种实现方式,虽然线程安全,但是也有很明显的问题,实例在一开始就被创建,浪费内存空间

2. 懒汉模式

public class P2 {
 
   private static P2 p2;
 
   private P2() {}
 
   public static P2 getP2() {
      if(p2==null) {
         p2 = new P2();
      }
      return p2;
   }
   
}

这种方式虽然在需要使用的时候才创建实例,但是很明显线程不安全,因为可能会有多个线程同时进入if代码块里

3. 加了锁的懒汉模式

public class P2 {
 
   private static P2 p2;

   private P2() {}
 
   public static P2 getP2() {
      synchronized (P2.class) {
         if(p2==null) {
            p2 = new P2();
         }
         return p2;
      }
   }
    
}

这种方式虽然在需要的时候,才创建了实例,也保证了线程安全,但是对创建实例和获取实例都加锁,效率很低

4. 双重锁模式

public class P2 {
 
   private volatile static P2 p2 = null;
 
   private P2() {}
 
   public static P2 getP2() {
      if(p2==null) {
         //1.
         synchronized (P2.class) {
            if(p2==null) {
               p2 = new P2();
            }
         }
     }
     return p2;
   }
    
}

在这种方式下,对p2进行了两次判断,第一次判断是防止在已经有p2实例的时候,线程还排队等锁,提高效率,第二次判断是防止创建多个p2实例,当并发量很高且一开始没有实例时,很多线程都会运行到<注释1>处,但是一开始只有一个线程能拿到锁,其它线程都在<注释1>处等着,当那个拿到锁的线程运行完创建好实例之后,其它线程陆续依次获得锁,但是接下来if(p2==null)就会防止它们继续创建实例,这样就表面来看确实实现了线程安全,而且接下来获取实例都没有加锁,提高了效率
但是大家细心就会发现,声明p2的时候加了volatile关键字,那么volatile在这里发挥了什么作用呢?

我们需要先分析一下jvm创建实例的过程:(也就是在执行new语句时它都干了什么
1. 在堆内存开辟内存空间
2. 在堆内存中实例化P2里面的各个参数
3. 把对象引用指向堆内存空间
而在多核CPU下,为了提高效率,JVM存在乱序执行优化现象,也就是说,在具体执行过程中,可能先执行步骤3再执行步骤2,当有一个线程在执行步骤2之前执行了步骤3,那么这个时候p2就不是null了,这个时候如果有其它线程要获取p2实例时,就直接获取走了(获取实例的时候不需要锁),但是这个实例是没有执行步骤2的实例,因此接下来在使用p2的时候可能会出错

但是加了volatile关键字之后,JVM的乱序执行优化就不会再执行了

5. 静态内部类单例模式

public class P2 {
 
   private P2() {}
 
   public static P2 getP2() {
      return Inner.p2;
   }
    
   private static class Inner {  
      private static P2 p2 = new P2();  
   } 
    
}

通过这种方式既也节约了空间,同时保证了线程安全
节约了空间是因为在加载P2类的时候,并不会同时加载内部类Inner,而是在调用getP2方法的时候才会对Inner类进行加载,同时实例化P2对象,也就是说要在使用的时候,p2实例才会被创建出来
由于内部类Inner只能被加载一次,而Inner加载的时候p2实例也被创建出来,因此它也可以保证线程安全
但是它也有一个很明显的缺陷,那就是对单例的传参并不方便

6. 枚举单例模式

public enum P2 {
 
   p2;
 
   public static P2 getInstance() {
      return P2.p2;
   }
    
}

枚举的方式不仅非常简洁,而且还由于枚举的特性,使得它是实现单例模式的最佳方式,因为枚举类实例不可通过反射的方式创建,而前面几种实现单例模式的方式虽然没有提供公开的构造方法,但是还是可以通过反射的方式创建更多实例,不仅如此,枚举实例在序列化前后也不会发生改变,而其它类型的对象在序列化之后默认是返回一个新的对象

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值