多线程环境中的单例模式

时间:2015-08-22 05:39:37

标签: java multithreading design-patterns singleton synchronized

在我的采访中,面试官以单身模式开始了他的问题。我在下面写道。然后,他问我们不应该在getInstance方法中检查Nullity吗?

我回答说,它是 NOT 必需,因为成员是静态类型并且正在同时初始化。但是,他似乎对我的回答不满意。我是否正确?

class Single {

        private final static Single sing = new Single();       
        private Single() {
        }        
        public static Single getInstance() {
            return sing;
        }
    }

现在,接下来的问题是他要求为多线程环境编写单例类。然后,我写了双重检查单例类。

  class MultithreadedSingle {        
        private static MultithreadedSingle single;       
        private MultithreadedSingle() {
        }        
        public static MultithreadedSingle getInstance() {
            if(single==null){
                    synchronized(MultithreadedSingle.class){
                      if(single==null){
                            single= new MultithreadedSingle(); 
                              }      
                      }
                   }
             return single;
        }
    }

然后,他反对使用synchronized并仔细检查并说它没用。你为什么要两次检查,为什么要使用synchronized?我试图用多种情况说服他。但是,他没有。

后来,在家里我尝试了下面的代码,我正在使用带有多个线程的简单单例类。

public class Test {

    public static void main(String ar[]) {
        Test1 t = new Test1();
        Test1 t2 = new Test1();
        Test1 t3 = new Test1();
        Thread tt = new Thread(t);
        Thread tt2 = new Thread(t2);
        Thread tt3 = new Thread(t3);
        Thread tt4 = new Thread(t);
        Thread tt5 = new Thread(t);
        tt.start();
        tt2.start();
        tt3.start();
        tt4.start();
        tt5.start();

    }
}

final class Test1 implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " : " + Single.getInstance().hashCode());
        }
    }

}
     class Single {

        private final static Single sing = new Single();       
        private Single() {
        }        
        public static Single getInstance() {
            return sing;
        }
    }

以下是输出:

Thread-0 : 1153093538
Thread-0 : 1153093538
Thread-0 : 1153093538
Thread-0 : 1153093538
Thread-0 : 1153093538
Thread-4 : 1153093538
Thread-1 : 1153093538
Thread-2 : 1153093538
Thread-3 : 1153093538
Thread-3 : 1153093538
Thread-3 : 1153093538
Thread-3 : 1153093538
Thread-3 : 1153093538
Thread-2 : 1153093538
Thread-2 : 1153093538
Thread-2 : 1153093538
Thread-2 : 1153093538
Thread-1 : 1153093538
Thread-1 : 1153093538
Thread-1 : 1153093538
Thread-1 : 1153093538
Thread-4 : 1153093538
Thread-4 : 1153093538
Thread-4 : 1153093538
Thread-4 : 1153093538

所以,问题是,是否有必要在多线程环境中使用synchronize或/和双重检查方法?看起来我的第一个代码本身(没有添加任何额外的代码行)是这两个问题的答案。任何更正和知识分享将不胜感激。

5 个答案:

答案 0 :(得分:6)

你的第一个例子绝对正确,通常是首选的成语&#34;对于单身人士。另一个是制作单元素枚举:

public enum Single {
    INSTANCE;

    ...
}

这两种方法非常相似,除非该类是Serializable,在这种情况下,枚举方法更容易正确 - 但如果类不是Serializable,我实际上更喜欢你的方法enum one,as风格问题。注意&#34;意外&#34;由于实现了接口或扩展了一个本身可序列化的类,因此成为Serializable。

对于双重检查锁示例中的第二次检查是否正确,你也是对的。但是,sing字段必须volatile才能使其在Java中运行;否则,没有正式的&#34;发生之前&#34;写入sing的一个线程和读取它的另一个线程之间的边缘。这可能导致第二个线程看到null,即使第一个线程分配给变量,或者,如果sing实例具有状态,它甚至可能导致第二个线程只看到某个状态(看到部分构造的对象)。

答案 1 :(得分:2)

1)Class#1适用于多线程环境

2)类#2是一个具有延迟初始化和双重检查锁定的单例,它是一种已知模式,需要使用同步。但是你的实现被破坏了,它需要volatile在该字段上。您可以在本文enter image description here

中找到原因

3)使用一种方法的Singleton不需要使用延迟模式,因为它的类只会在第一次使用时加载和初始化。

答案 2 :(得分:1)

你的第一个答案似乎对我有好处,因为没有任何竞争条件的机会。

至于知识共享,在Java中实现单例的最佳方法是使用Enum。创建一个只包含一个实例的枚举,就是这样。至于代码示例 -

public enum MyEnum {
    INSTANCE;

    // your other methods
}

从好书Effective Java -

  

[....]这种方法在功能上等同于公共领域方法,除了它更简洁,免费提供序列化机制,并提供防止多个实例化的铁定保证,即使面对复杂的序列化或反射攻击。[...]单元素枚举类型是实现单例的最佳方式。

答案 3 :(得分:0)

根据Double-checked_locking,它可能是最好的方式

$data = file_get_contents("php://input");

$objData = json_decode($data);

$key =  $objData->data;
if(!empty($key)){

    $sql = "SELECT filmName as film, url as param, imagePath as image FROM films WHERE filmName LIKE '%$key%' LIMIT 5";

    $result =   mysqli_query($conn, $sql) or die(mysqli_error($conn));
    $output = array();
    while ($row = mysqli_fetch_array($result)) {
        $output[] = $row;

    }
    echo json_encode($output);
}

或使用Initialization-on-demand holder idiom

class Foo {
    private volatile Helper helper;
    public Helper getHelper() {
        Helper result = helper;
        if (result == null) {
            synchronized(this) {
                result = helper;
                if (result == null) {
                    helper = result = new Helper();
                }
            }
        }
        return result;
    }
}

答案 4 :(得分:0)

在#2的情况下,将'volatile'关键字添加到静态字段'single'。

使用Double-Checked Locking

时,请考虑这种情况
  1. 线程A首先进入并获得锁,然后继续初始化对象。
  2. 根据Java内存模型(JMM),为变量分配内存并在初始化Java对象之前对其进行发布。
  3. 线程B进入,由于再次检查了锁定,并且变量被初始化,因此它不获取锁定。
  4. 这不能保证对象已初始化,即使这样,每个cpu缓存也可能不会更新。请参阅Cache Coherence

现在进入volatile关键字。

易失性变量始终写入主存储器。因此,没有缓存不一致性。