1、简介
双重检查锁单例:使用双重检查锁机制来实现懒汉式单例。它的基本思想是使用两次if 判断,保证在多线程环境下创建单例对象时不会出现竞态条件。
2、优缺点
双重检查锁机制的优点是,在单例对象已经初始化之后,就不需要再进行同步。这样可以提高系统性能。但是,双重检查锁机制的实现过程复杂,需要注意一些细节。例如,使用volatile 关键字修饰 INSTANCE 变量,以及在获取单例对象时使用双重 if 判断。
3、代码实现
下面是使用双重检查锁机制的 Java 代码示例:
1. public class Singleton { 2. private static volatile Singleton INSTANCE; 3. 4. private Singleton() {} 5. 6. public static Singleton getInstance() { 7. if (INSTANCE == null) { 8. synchronized (Singleton.class) { 9. if (INSTANCE == null) { 10. INSTANCE = new Singleton(); 11. } 12. } 13. } 14. return INSTANCE; 15. } 16. }
4、为什么使用volatile
在代码示例中,使用了volatile 关键字修饰 INSTANCE 变量。这是因为,在 Java 语言中,当多个线程并发执行时,会发生指令重排序。
如果不使用 volatile 关键字,就可能会出现创建单例对象的过程被拆分成多个步骤,例如:
①为单例对象分配内存空间。
②初始化单例对象。
③将INSTANCE 变量指向分配的内存空间。
在没有volatile 关键字的情况下,步骤 ② 和 ③ 可能会被重排序。
这就可能导致其他线程在执行getInstance() 方法时,看到的 INSTANCE 变量已经被赋值,但单例对象并没有被完成初始化。
使用 volatile 关键字,可以保证在多线程环境下,单例对象的初始化过程是串行化的,从而避免了竞态条件。
5、volatile详解
在Java 语言中,volatile 关键字是一种轻量级的同步机制。它主要用于保证每个线程对于变量的读取都是最新的,保证了变量的可见性。
当一个线程对于一个volatile 变量进行修改时,Java 虚拟机会在该线程将修改后的值存回主存之前,强制将该线程中其他变量的缓存数据写回主存。而其他线程在访问该 volatile 变量时,将强制从主存中重新读取该变量的值,这样就能保证变量的可见性。
需要注意的是,volatile 关键字只能保证变量的可见性,不能保证原子性。
如果多个线程同时对同一个 volatile 变量进行修改,可能会出现竞态条件。而且volatile关键字不能替代锁机制来保证线程安全性。如果多个线程同时对一个变量进行修改,而该变量的状态是不确定的,那么就可能会出现问题。
另外, volatile 不能保证复合操作的原子性,如 i++ 不是原子性的操作,volatile 也无法保证原子性。
总结:使用volatile 保证了变量在多线程中可见性,但是不能保证原子性,还需要借助锁机制来实现线程安全。
6、volatile举例说明
当一个变量被volatile 修饰,它会告诉 JVM,这个变量可能会被多线程共享,因此需要进行特殊的处理。
下面是一个使用volatile 关键字的例子:
1. public class VolatileExample { 2. private volatile boolean flag = false; 3. public void setFlag(){ 4. this.flag = true; 5. } 6. public void printValue() { 7. if (flag) { 8. System.out.println("flag is true"); 9. } 10. } 11. }
在这个例子中,flag 是一个共享变量,可能会被多个线程修改。由于flag被volatile修饰,在这里保证多线程修改时读取到最新值。
常用于共享变量的内存可见性,如果没有volatile可能会出现每个线程读取到的变量值不同,或者其他线程不能及时获取到共享变量的最新值,这样会导致错误的结果或系统不稳定。
需要注意的是,volatile并不能保证原子性,如果需要保证变量的操作具有原子性,还需要配合使用synchronized或者Atomic类型。
此外,Java5以后引入了类java.util.concurrent.atomic.AtomicInteger, AtomicLong, AtomicBoolean, 等类型,它们都有一个显式的变量值和内存屏障(memory barrier),可以在多线程下保证变量更新的可见性和原子性,可以用来代替volatile.
在使用volatile关键字时,编译器与运行时会对它所修饰的变量的访问进行特殊的处理。
编译器会禁止重排序优化,会强制在每次读取该变量的时候,都直接从内存中获取最新的值。
这样可以确保在多线程环境下,所有线程都能够看到共享变量的最新值。
总的来说,使用volatile 关键字的目的是保证共享变量的内存可见性和禁止指令重排序,但不能保证变量操作的原子性,如果需要保证原子性可以使用 Atomic 类型或者加锁。