单例模式—-双重检查锁

  • Post author:
  • Post category:其他





双重检查锁

双重检查锁:

双检锁:单例模式中用volatile和synchronized来满足双重检查锁机制;

在实现单例模式的时候往往会忽略掉多线程的情况,就是写的代码在单线程的情况下是没问题的,但是一碰到多个线程的时候,由于代码没写好,就会引发很多问题,而且这些问题都是很隐蔽和很难排查的。而volatile(java5):可以保证多线程下的可见性;



二次判空原因

第一次判断是为了验证是否创建对象,判断为了避免不必要的同步

第二次判断是为了避免重复创建单例,因为可能会存在多个线程通过了第一次判断在等待锁,来创建新的实例对象。

判断是为了在null的情况下创建实例代码会检查两次单例类是否有已存在的实例,一次加锁一次不加锁,一次确保不会有多个实例被创建。单例模式中用volatile和synchronized来满足双重检查锁机制

public class SingletonMode {


/*

volatile关键字: 保持内存的可见性–所有线程都能获取的共享内存的最新状态

* 理解:

* 不用volatile的状态: 老板让多个人共同完成一件事(初始状态为进行中),假设

* 其中某一个人完成后,事件状态变成了已完成,但是 其他人不知道这件事已完成,认为该事件依然处于进行中

* 这就造成了事情的状态与真实状态不同步的现象。

* 使用volatile的状态:板让多个人共同完成一件事(初始状态为进行中),假设

* 其中某一个人完成后,事件状态变成了已完成;此时

* 其他人立刻从老板那接到了事情已完成的通知,于是都明确了

* 这件事已经完成了。

*

* volatile的作用:每次读取前必须先从主存刷新最新的值

* 每次写入后必须立即同步回主存当中

*/

private static volatile SingletonMode singletonMode;

//构造函数修饰为private防止在其他地方创建该实例
private SingletonMode() {
}

/**
 * 有的代码中会将同步锁synchronized写在方法中,例如:
 *    public static synchronized SingletonMode getInstance(){.....}
 * 造成的弊端就是:多线程每次在调用getInstance()时都会产生一个同步,造成损耗。
 * 相应的我们需要保持同步的代码块仅仅就是:
 *      singletonMode = new SingletonMode();
 * 所以只要在该代码处添加同步锁就可以了
 *
 */
public static SingletonMode getInstance() {
    /**
     * 此处检测singletonMode == null,是为了防止当singletonMode已经初始化后,
     * 还会继续调用同步锁,造成不必要的损耗
     */
    if (singletonMode == null) {
        // 加锁目的,防止多线程同时进入造成对象多次实例化
        synchronized (SingletonMode.class) {
            // 为了在null的情况下创建实例,防止不必要的损耗
            if (singletonMode == null) {
                singletonMode = new SingletonMode();
            }
        }
    }
    // 返回实例对象
    return singletonMode;
}

此类的设计确保只创建一个 Singleton 对象。构造函数被声明为 private,getInstance() 方法只创建一个对象。这个实现适合于单线程程序。

然而,当引入多线程时,就必须通过同步来保护 getInstance() 方法。如果不保护 getInstance() 方法,则可能返回Singleton 对象的两个不同的实例。

假设两个线程并发调用 getInstance() 方法并且按以下顺序执行调用:

 public static Singleton getInstance()
  {
    if (instance == null)          //1
      instance = new Singleton();  //2
    return instance;               //3
  }
}
线程 1 调用 getInstance() 方法并决定 instance 在 //1 处为 null。 

线程 1 进入 if 代码块,但在执行 //2 处的代码行时被线程 2 预占。 

线程 2 调用 getInstance() 方法并在 //1 处决定 instance 为 null。 

线程 2 进入 if 代码块并创建一个新的 Singleton 对象并在 //2 处将变量 instance 分配给这个新对象。 

线程 2 在 //3 处返回 Singleton 对象引用。

线程 2 被线程 1 预占。 

线程 1 在它停止的地方启动,并执行 //2 代码行,这导致创建另一个 Singleton 对象。 

线程 1 在 //3 处返回这个对象。

结果是 getInstance() 方法创建了两个 Singleton 对象,而它本该只创建一个对象。

通过同步 getInstance() 方法从而在同一时间只允许一个线程执行代码

此类的设计确保只创建一个 Singleton 对象。

构造函数被声明为 private,getInstance() 方法只创建一个对象。这个实现适合于单线程程序。然而,当引入多线程时,就必须通过同步来保护 getInstance() 方法。如果不保护 getInstance() 方法,则可能返回Singleton 对象的两个不同的实例。

假设两个线程并发调用 getInstance() 方法并且按以下顺序执行调用:

线程 1 调用 getInstance() 方法并决定 instance 在 //1 处为 null。 

线程 1 进入 if 代码块,但在执行 //2 处的代码行时被线程 2 预占。 

线程 2 调用 getInstance() 方法并在 //1 处决定 instance 为 null。 

线程 2 进入 if 代码块并创建一个新的 Singleton 对象并在 //2 处将变量 instance 分配给这个新对象。 

线程 2 在 //3 处返回 Singleton 对象引用。

线程 2 被线程 1 预占。 

线程 1 在它停止的地方启动,并执行 //2 代码行,这导致创建另一个 Singleton 对象。 

线程 1 在 //3 处返回这个对象。



线程安全的 getInstance() 方法

public static synchronized Singleton getInstance()
{
  if (instance == null)          //1
    instance = new Singleton();  //2
  return instance;               //3
}

清单 2 中的代码针对多线程访问 getInstance() 方法运行得很好。然而,当分析这段代码时,您会意识到只有在第一次调用方法时才需要同步。由于只有第一次调用执行了

//2 处的代码,而只有此行代码需要同步,因此就无需对后续调用使用同步。所有其他调用用于决定 instance 是非 null 的,并将其返回。多线程能够安全并发地执行除第一次调用外的所有调用。尽管如此,由于该方法是synchronized 的,需要为该方法的每一次调用付出同步的代价,即使只有第一次调用需要同步。

为使此方法更为有效,一个被称为双重检查锁定的习语就应运而生了。这个想法是为了避免对除第一次调用外的所有调用都实行同步的昂贵代价。同步的代价在不同的 JVM 间是不同的。在早期,代价相当高。随着更高级的 JVM 的出现,同步的代价降低了,但出入synchronized 方法或块仍然有性能损失

读volatile:每当子线程某一语句要用到volatile变量时,都会从主线程重新拷贝一份,这样就保证子线程的会跟主线程的一致。

写volatile: 每当子线程某一语句要写volatile变量时,都会在读完后同步到主线程去,这样就保证主线程的变量及时更新。

正确的双重检查锁定模式需要需要使用 volatile。volatile主要包含两个功能。

1:使用 volatile定义的变量,将会保证对所有线程的可见性。2:禁止指令重排序优化。

由于 volatile禁止对象创建时指令之间重排序,所以其他线程不会访问到一个未初始化的对象,从而保证安全性。

对象的创建可能发生指令的重排序,使用 volatile可以禁止指令的重排序,保证多线程环境内的系统安全。


双重检查锁:


双检锁:单例模式中用volatile和synchronized来满足双重检查锁机制;

在实现单例模式的时候往往会忽略掉多线程的情况,就是写的代码在单线程的情况下是没问题的,但是一碰到多个线程的时候,由于代码没写好,就会引发很多问题,而且这些问题都是很隐蔽和很难排查的。而volatile(java5):可以保证多线程下的可见性;

双重检查锁:双重锁进行了两次判空:第一层判断为了避免不必要的同步,第二层判断则是为了在null的情况下创建实例代码会检查两次单例类是否有已存在的实例,一次加锁一次不加锁,一次确保不会有多个实例被创建。单例模式中用volatile和synchronized来满足双重检查锁机制

读volatile:每当子线程某一语句要用到volatile变量时,都会从主线程重新拷贝一份,这样就保证子线程的会跟主线程的一致。

写volatile: 每当子线程某一语句要写volatile变量时,都会在读完后同步到主线程去,这样就保证主线程的变量及时更新。

正确的双重检查锁定模式需要需要使用 volatile。volatile主要包含两个功能。

1:使用 volatile定义的变量,将会保证对所有线程的可见性。2:禁止指令重排序优化。

由于 volatile禁止对象创建时指令之间重排序,所以其他线程不会访问到一个未初始化的对象,从而保证安全性。

对象的创建可能发生指令的重排序,使用 volatile可以禁止指令的重排序,保证多线程环境内的系统安全。



版权声明:本文为weixin_50005657原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。