单例模式 - 大白话

🙌🏻 最近有小老弟吐槽,面试回答单例模式不尽人意,被面试官pass了,趁此机会,我们来简单总结一波究竟什么是单例模式。

1、什么是单例模式

单例模式:顾名思义就是在整个运行时域,一个类只有一个实例对象,属于创建类型的一种常用的软件设计模式。通过单例模式的方法创建的类在当前进程中只有一个实例(根据需要,也有可能一个线程中属于单例,如:仅线程上下文内使用同一个实例)但是:在设计单例的时候要考虑很多问题,比如线程安全问题、序列化对单例的破坏等

2、为什么需要单例模式呢?

因为有的类的实例对象的创建和销毁对资源来说消耗不大(比如说String),有的类型呢,比较庞大和复杂,如果频繁的创建和销毁对象,并且这些对象是可以复用的,那么将会造成一些不必要的性能浪费。

🙋‍♂️个🌰子:比如说访问数据库,而创建数据库链接对象是一个耗资源的操作,并且数据库连接完全是可以复用的,那么我可以将这个对象设计成单例的,这样我只需要创建一次并且重复使用这个对象就行了,而不需要每次都去访问。

3、在java中如何实现单例模式呢?

3.1、常规懒加载代码如下(非线程安全)

public class Singleton {
     private Singleton({} // 构造器私有
     private static Singleton instance = null;
     public static Singleton getInstance({
          if( instance == null) {
              instance = new Singleton();
          }
          return instance;
     }
}

3.2、懒加载-线程安全改造1

public class Singleton {
     private Singleton({}
     private static Singleton instance = null;
     public static synchronized Singleton getInstance({
          if( instanice == null) {
              instance = new Singleton();
          }
          return instance;
     }
}

但是加入了sync有个问题,其实我们只想要对象在构建的时候同步线程,而这样的话,每次在获取对象时就都要进行同步操作,对性能影响非常大

3.3、[非]懒加载-线程安全改造2

线程安全问题出现在构建对象阶段,那么我们只要在编译期构建对象,在运行时调用,就不用考虑安全问题了。于是我们可以这么写:

public class Singleton {
       private static Singleton instance = new Singleton();
       private Singleton (){}
       public static Singleton getInstance(){
           return instance;
       }
}

这么写能保证线程安全,但这不是懒加载的 - - 不推荐

3.4、懒加载-线程安全改造3(双检锁写法)

在安全改造1的基础上,形成低效的原因是 getInstance()方法中加入了synchronized, 所以每个进行该方法的线程首先都得获取到锁, 锁的粒度太大了。

public class Singleton {
     private static Singleton singleton; // 定义对象
     private Singleton ( ){}
     public static Singleton getSingleton({
          if(singleton == null){// 第一步 常规的非空判断,没有对象才会去创建对象
                 synchronized (Singleton.class){
                     // 第二次判空,防止多个线程同时进来没拿到锁阻塞到第一步,进到这里的时候会创建多个对象
                     if (singleton == null) {
                         singleton = new Singleton(); // 第三步
                      }
                 }
          }
          return singleton;
      }
}

3.5、懒加载-线程安全改造4

双检锁已经很安全了,但是!(这里就要谈到Java多线程的happens-before原则)

happens-before原则有8条:

  • 1、程序顺序规则
  • 2、锁定规则
  • 3、volatile变量规则
  • 4、线程启动规则
  • 5、线程结束规则
  • 6、中断规则
  • 7、终结器规则
  • 8、传递性规则

因为在上面第三步中,singleton = new Singleton();不是原子操作,此行代码总共要执行三条指令:1、分配内存。2、初始化对象。3、对象指向内存地址。在真正执行时,JVM虚拟机为了效率可能会对指令进行重排,比如说按照3、1、2的方式执行。那么怎么改进呢。此时用到另外一个关键字 volatile-防止指令重排 ,防止a线程执行到new Singleton()的时候此时对象还未创建,只是把内存地址申请下来了,然后b线程拿到锁又进来了,b线程以为这个对象已经创建完成,在第二个if (singleton == null)处进行判断为falseb线程会直接return, 这个时候就会有问题了,b调用getInstance方法会报错,因为出现了线程不安全的问题。(这也是指令重排会导致的有序性问题)

 public class Singleton {
     private volatile static Singleton singleton; // 定义对象
     private Singleton ( ){}
     public static Singleton getSingleton({
          if(singleton == null){// 第一步 常规的非空判断,没有对象才会去创建对象
                 synchronized (Singleton.class){
                     // 第二次判空,虽然只有一个线程能拿到锁,但是多个线程很有可能已经进入了if代码块,此时正在等待,假设两个线程a、b      
                     // a先拿到锁,一旦线程a释放,线程b会立即获得锁,然后又进行对象创建,这样对象会被创建多次。
                     if (singleton == null) {
                         singleton = new Singleton(); // 第三步 在指令层面不是一个原子操作
                      }
                 }
          }
          return singleton;
      }
}

3.6、既满足懒加载又满足线程安全-静态内部类

静态内部类在程序启动的时候不会加载,只有第一次调用的时候才会加载,这种写法巧妙的利用了JDK类加载的机制的特性来实现了懒加载。

public class Singleton {
     private static class SingletonHolder {
     private static final Singleton INSTANCE = new Singleton();
     private Singleton (){}
     public static final Singleton getInstance() {
           return SingletonHolder.INSTANCE;
     }
}

3.7、天然线程安全的类-枚举单例

public enum Singleton {  
    EXAMPLE_INSTANCE;  
    public void testMethod() {  
    }  
} 

为什么枚举是线程安全的?当我们反编译该class文件: javap -c Singleton.class

结果如下:

public final class Singleton extends java.lang.Enum {

	public static final Singleton EXAMPLE_INSTANCE;

	...(省略)
}

可以看到Singleton.java 是继承了Enum类的,同时被final关键字修饰:这个类是不能被继承的。并且EXAMPLE_INSTANCE通过static修饰,static类型的属性会在类被加载之后才被初始化,当一个Java类第一次被真正使用到的时候静态资源被初始化、Java类的加载和初始化过程都是线程安全的。所以,创建一个enum类型是线程安全的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值