多线程环境中的单例模式

Rav*_*avi 11 java singleton multithreading design-patterns synchronized

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

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

class Single {

        private final static Single sing = new Single();       
        private Single() {
        }        
        public static Single getInstance() {
            return sing;
        }
    }
Run Code Online (Sandbox Code Playgroud)

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

  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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

然后,他对使用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;
        }
    }
Run Code Online (Sandbox Code Playgroud)

以下是输出:

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
Run Code Online (Sandbox Code Playgroud)

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

ysh*_*vit 6

你的第一个例子是绝对正确的,通常是单身人士的首选"成语".另一个是制作单元素枚举:

public enum Single {
    INSTANCE;

    ...
}
Run Code Online (Sandbox Code Playgroud)

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

在双重检查的锁示例中,您对第二次检查是否正确也是正确的.但是,该sing字段必须volatile为了在Java中工作; 否则,在一个线程写入sing和另一个线程读取之间没有正式的"先发生"边缘.这可能导致第二个线程看到null即使第一个线程分配给变量,或者,如果sing实例具有状态,它甚至可能导致第二个线程仅看到某个状态(看到部分构造的对象).

  • 至于enum方法,它主要是风格.如果某人序列化你的单例然后反序列化它就会出现序列化问题 - 现在有两个实例,而且它不是单例!同样,有一些方法可以防范这种情况,但即便如此,它也会变得有点棘手; 最简单的方法是将它标记为枚举,让JVM为您处理序列化实例解析. (3认同)