单例模式的双重加锁实现
本⽂从单例模式的⼀般实现⽅式开始说起,逐步深⼊到双重加锁实现。
1. ⾸先介绍⼀下最简单的单例模式——饿汉模式,这种⽅式在单例类被加载的时候实例化。代码实现如下:
1public class Singleton {
2private static Singleton instance;
3
4static {
5        instance = new Singleton();java单例模式双重锁
6    }
7
8private Singleton() {
9    }
10
11public static Singleton getInstance() {
12return instance;
13    }
14 }
饿汉模式的缺点在于,如果单例对象的创建过程⽐较耗时,那么应⽤程序的启动将会⽐较慢。
2. 为了克服饿汉模式的缺点,将单例对象的创建过程延后到第⼀次使⽤单例对象时,这种实现⽅式被称为懒汉模式。代码实现如下:
1public class Singleton {
2private static Singleton instance;
3
4private Singleton() {
5    }
6
7public static Singleton getInstance() {
8if (instance == null) {
9            instance = new Singleton();
10        }
11
12return instance;
13    }
14 }
需要注意的是这种实现⽅式是线程不安全的。假设在单例类被实例化之前,有两个线程同时在获取单例对象,线程1在执⾏完第8⾏ if (instance == null) 后,线程调度机制将 CPU 资源分配给线程2,此时线程2在执⾏第8⾏  if (instance == null) 时也发现单例类还没有被实例化,这样就会导致单例类被实例化两次。为了防⽌这种情况发⽣,需要对 getInstance() ⽅法同步。下⾯看改进后的懒汉模式:
1public class Singleton {
2private static Singleton instance;
3
4private Singleton() {
5    }
6
7// 线程安全的懒汉模式
8public synchronized static Singleton getInstance() {
9if (instance == null) {
10            instance = new Singleton();
11        }
12
13return instance;
14    }
15 }
3. 双重加锁(double check)
第2种实现⽅式中,每次获取单例对象时都会加锁,这样就会带来性能损失。双重加锁实现本质也是⼀种懒汉模式,相⽐第2种实现⽅式将会有较⼤的性能提升。代码实现如下:
1public class Singleton {
2private volatile static Singleton instance;
3
4private Singleton() {
5    }
6
7public static Singleton getInstance() {
8if (instance == null) {
9synchronized (Singleton.class) {
10if (instance == null) {
11                    instance = new Singleton();
12                }
13            }
14        }
15
16return instance;
17    }
18 }
就算在单例类被实例化时有多个线程同时通过了第8⾏代码 if (instance == null) 的判断,但同⼀时间只有⼀个线程获得锁后进⼊临界区。通过第8⾏判断的每个线程会依次获得锁进⼊临界区,所以进⼊临界区后还要再判断⼀次单例类是否已被其它线程实例化,以避免多次实例化。由于双重加锁实现仅在实例化单例类时需要加锁,所以相较于第2种实现⽅式会带来性能上的提升。另外需要注意的是双重加锁要对instance 域加上volatile修饰符。由于 synchronized 并不是对 instance 实例进⾏加锁(因为现在还并没有实例),所以线程在执⾏完第11⾏修改 instance 的值后,应该将修改后的 instance ⽴即写⼊主存(main memory),⽽不是暂时存在寄存器或者⾼速缓冲区(caches)中,以保证新的值对其它线程可见。
补充:第9⾏可以锁住任何⼀个对象,要进⼊临界区必须获得这个对象的锁。由于并不知道其它对象的
锁的⽤途,所以这⾥最好的⽅式是对 Singleton.class 进⾏加锁。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。