双重检查锁实现单例(java)

单例类在Java开发者中很是经常使用,可是它给初级开发者们形成了不少挑战。他们所面对的其中一个关键挑战是,怎样确保单例类的行为是单例?也就是说,不管任何缘由,如何防止单例类有多个实例。在整个应用生命周期中,要保证只有一个单例类的实例被建立,双重检查锁(Double checked locking of Singleton)是一种实现方法。顾名思义,在双重检查锁中,代码会检查两次单例类是否有已存在的实例,一次加锁一次不加锁,一次确保不会有多个实例被建立。顺便提一下,在JDK1.5中,Java修复了其内存模型的问题。在JDK1.5以前,这种方法会有问题。本文中,咱们将会看到怎样用Java实现双重检查锁的单例类,为何Java 5以前的版本双重检查锁会有问题,以及怎么解决这个问题。顺便说一下,这也是重要的面试要点,我曾经在金融业和服务业的公司面试被要求手写双重检查锁实现单例模式、相信我,这很棘手,除非你清楚理解了你在作什么。你也能够阅读个人完整列表“单例模式设计问题”来更好的准备面试。html

为何你须要双重检查锁来实现单例类?

一个常见情景,单例类在多线程环境中违反契约。若是你要一个新手写出单例模式,可能会获得下面的代码:java

复制代码
1 private static Singleton _instance;
2  
3 public static Singleton getInstance() {
4     if (_instance == null) {
5         _instance = new Singleton();
6     }
7     return _instance;
8 }
复制代码

 

而后,当你指出这段代码在超过一个线程并行被调用的时候会建立多个实例的问题时,他极可能会把整个getInstance()方法设为同步(synchronized),就像咱们展现的第二段示例代码getInstanceTS()方法同样。尽管这样作到了线程安全,而且解决了多实例问题,但并不高效。在任何调用这个方法的时候,你都须要承受同步带来的性能开销,然而同步只在第一次调用的时候才被须要,也就是单例类实例建立的时候。这将促使咱们使用双重检查锁模式(double checked locking pattern),一种只在临界区代码加锁的方法。程序员称其为双重检查锁,由于会有两次检查 _instance == null,一次不加锁,另外一次在同步块上加锁。这就是使用Java双重检查锁的示例:程序员

复制代码
 1 public static Singleton getInstanceDC() {
 2     if (_instance == null) {                // Single Checked
 3         synchronized (Singleton.class) {
 4             if (_instance == null) {        // Double checked
 5                 _instance = new Singleton();
 6             }
 7         }
 8     }
 9     return _instance;
10 }
复制代码

 

这个方法表面上看起来很完美,你只须要付出一次同步块的开销,但它依然有问题。除非你声明_instance变量时使用了volatile关键字。没有volatile修饰符,可能出现Java中的另外一个线程看到个初始化了一半的_instance的状况,但使用了volatile变量后,就能保证先行发生关系(happens-before relationship)。对于volatile变量_instance,全部的写(write)都将先行发生于读(read),在Java 5以前不是这样,因此在这以前使用双重检查锁有问题。如今,有了先行发生的保障(happens-before guarantee),你能够安全地假设其会工做良好。另外,这不是建立线程安全的单例模式的最好方法,你能够使用枚举实现单例模式,这种方法在实例建立时提供了内置的线程安全。另外一种方法是使用静态持有者模式(static holder pattern)。面试

复制代码
 1 /*
 2  * A journey to write double checked locking of Singleton class in Java.
 3  */
 4  
 5 class Singleton {
 6  
 7     private volatile static Singleton _instance;
 8  
 9     private Singleton() {
10         // preventing Singleton object instantiation from outside
11     }
12  
13     /*
14      * 1st version: creates multiple instance if two thread access
15      * this method simultaneously
16      */
17  
18     public static Singleton getInstance() {
19         if (_instance == null) {
20             _instance = new Singleton();
21         }
22         return _instance;
23     }
24  
25    /*
26     * 2nd version : this definitely thread-safe and only
27     * creates one instance of Singleton on concurrent environment
28     * but unnecessarily expensive due to cost of synchronization
29     * at every call.
30     */
31  
32    public static synchronized Singleton getInstanceTS() {
33        if (_instance == null) {
34            _instance = new Singleton();
35        }
36        return _instance;
37    }
38  
39    /*
40     * 3rd version : An implementation of double checked locking of Singleton.
41     * Intention is to minimize cost of synchronization and  improve performance,
42     * by only locking critical section of code, the code which creates instance of Singleton class.
43     * By the way this is still broken, if we don't make _instance volatile, as another thread can
44     * see a half initialized instance of Singleton.
45     */
46  
47     public static Singleton getInstanceDC() {
48         if (_instance == null) {
49             synchronized (Singleton.class) {
50                 if (_instance == null) {
51                     _instance = new Singleton();
52                 }
53             }
54         }
55         return _instance;
56     }
57 }
复制代码

 

这就是本文的全部内容了。这是个用Java建立线程安全单例模式的有争议的方法,使用枚举实现单例类更简单有效。我并不建议你像这样实现单例模式,由于用Java有许多更好的方式。可是,这个问题有历史意义,也教授了并发是如何引入一些微妙错误的。正如以前所说,这是面试中很是重要的一点。在去参加任何Java面试以前,要练习手写双重检查锁实现单例类。这将加强你发现Java程序员们所犯编码错误的洞察力。另外,在如今的测试驱动开发中,单例模式因为难以被模拟其行为而被视为反模式(anti pattern),因此若是你是测试驱动开发的开发者,最好避免使用单例模式。安全

相关文章
相关标签/搜索