java 多线程环境中的单例模式
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/32152745/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Singleton Pattern in Multi threaded environment
提问by Ravi
During my interview, interviewer started his question with singleton pattern. I wrote below. Then, he asked Shouldn't we check for Nullity inside getInstance
method?
在我的面试中,面试官以单例模式开始他的问题。我写在下面。然后,他问我们不应该检查getInstance
方法内部的 Nullity吗?
I replied with, It is NOTnecessary, since member is static type and is being initialized at the same time. But, seems like he was not satisfied with my answer.Am I correct or not ?
我回答说,没有必要,因为成员是静态类型并且同时被初始化。但是,他似乎对我的回答不满意。我说得对还是不对?
class Single {
private final static Single sing = new Single();
private Single() {
}
public static Single getInstance() {
return sing;
}
}
Now, next question he ask to write singleton class for multi-threaded environment. Then, I wrote double check singleton class.
现在,他要求为多线程环境编写单例类的下一个问题。然后,我写了双重检查单例类。
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;
}
}
Then, he had an objection with using synchronized
and double check and said It is useless. Why are you checking twice and why are you using synchronized ?I tried to convince him with multiple scenario. But, he didn't.
然后,他反对使用synchronized
并再次检查并说它没有用。为什么要检查两次,为什么要使用 synchronized ?我试图用多种情况来说服他。但是,他没有。
Later, at home I tried below code where I'm using simple singleton class with multiple thread.
后来,在家里,我尝试了下面的代码,其中我使用了多线程的简单单例类。
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;
}
}
Below is the output :
以下是输出:
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
So, question is, Is it necessary to use synchronize
or/and double check method in multi-threaded environment ? It seems like my first code itself (without adding any extra line of code) was the answer for both question. Any correction and knowledge share will be appreciated.
所以,问题是,是否有必要synchronize
在多线程环境中使用或/和双重检查方法?似乎我的第一个代码本身(没有添加任何额外的代码行)就是这两个问题的答案。任何更正和知识共享将不胜感激。
采纳答案by yshavit
Your first example is absolutely correct, and is usually the preferred "idiom" for singletons. The other one is to make a single-element enum:
你的第一个例子是绝对正确的,通常是单身人士的首选“习语”。另一种是制作单元素枚举:
public enum Single {
INSTANCE;
...
}
The two approaches are pretty similar unless the class is Serializable, in which case the enum approach is much easier to get right -- but if the class isn't Serializable, I actually prefer your approach the enum one, as a stylistic matter. Watch out for "accidentally" becoming Serializable due to implementing an interface or extending a class which is itself Serializable.
这两种方法非常相似,除非类是可序列化的,在这种情况下,枚举方法更容易正确 - 但如果类不是可序列化的,我实际上更喜欢您的方法枚举方法,作为一种风格问题。当心由于实现接口或扩展本身可序列化的类而“意外地”成为可序列化的。
You are also right about the second check for nullity in the double-checked lock example. However, the sing
field mustbe volatile
for this to work in Java; otherwise, there is no formal "happens-before" edge between one thread writing to sing
and another thread reading to it. This can result in that second thread seeing null
even though the first thread assigned to the variable, or, if the sing
instance has state, it could even result in that second thread seeing only some of that state (seeing a partially-constructed object).
您对双重检查锁示例中的第二次无效检查也是正确的。但是,该sing
字段必须是volatile
在 Java 中工作的;否则,在一个线程写入sing
和另一个线程读取它之间没有正式的“happens-before”边缘。这可能导致第二个线程看到null
分配给变量的第一个线程,或者,如果sing
实例具有状态,它甚至可能导致第二个线程只看到该状态的一部分(看到部分构造的对象)。
回答by Evgeniy Dorofeev
1) Class #1 is good for multithreaded environment
1) Class #1 适用于多线程环境
2) Class #2 is a singleton with lazy initialization and double checked locking, it's a known pattern and it needs to use synchronization. But your implementation is broken, it needs volatile
on the field. You can find out why in this article http://www.javaworld.com/article/2074979/java-concurrency/double-checked-locking--clever--but-broken.html
2) 类#2 是具有延迟初始化和双重检查锁定的单例,它是一种已知模式,需要使用同步。但是你的实施被打破了,它需要volatile
在现场。你可以在这篇文章中找到原因http://www.javaworld.com/article/2074979/java-concurrency/double-checked-locking--clever--but-broken.html
3) a Singleton with one method does not need to use lazy pattern, because its class will be loaded and initialized only at first usage.
3) 有一个方法的 Singleton 不需要使用惰性模式,因为它的类只会在第一次使用时加载和初始化。
回答by MD Sayem Ahmed
Your first answer seems to be good for me, as there are no chance of a race condition whatsoever.
您的第一个答案似乎对我有好处,因为根本不可能出现竞争状况。
As for knowledge share, the best approach to implement a singleton in Java is using Enum. Create an enum with exactly one instance, and that's it. As for code sample -
至于知识共享,在 Java 中实现单例的最佳方法是使用 Enum。创建一个只有一个实例的枚举,就是这样。至于代码示例 -
public enum MyEnum {
INSTANCE;
// your other methods
}
From the good book Effective Java-
从好书Effective Java-
[....] This approach is functionally equivalent to the public field approach, except that it is much more concise, provides the serialization machinery for free, and provides an ironclad guarantee against multiple instantiation, even in the face of sophisticated serialization or reflection attacks.[...] a single-element enum type is the best way to implement a singleton.
[....] 这种方法在功能上等同于公共字段方法,除了它更简洁,免费提供序列化机制,并提供针对多重实例化的铁定保证,即使面对复杂的序列化或反射攻击。[...] 单元素枚举类型是实现单例的最佳方式。
回答by Nassim MOUALEK
according to Double-checked_locking, its probably the best way
根据Double-checked_locking,这可能是最好的方法
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;
}
}
or using the Initialization-on-demand holder idiom
或使用按需初始化持有人习语
public class Something {
private Something() {}
private static class LazyHolder {
private static final Something INSTANCE = new Something();
}
public static Something getInstance() {
return LazyHolder.INSTANCE;
}
}
回答by Kamaal
In case #2 add 'volatile' keyword to the static field 'single'.
如果 #2 将 'volatile' 关键字添加到静态字段 'single'。
Consider this scenario when using Double-Checked Locking
使用双重检查锁定时考虑这种情况
- Thread A comes in first and gets the lock and proceeds to initialize the object.
- According to Java Memory Model (JMM), memory is allocated for a variable and published before initializing the Java Object.
- Thread B comes in and because of the double checked locking, and the variable is initialized, it does not acquire the lock.
- This does not guarantee that the object is initialized and even if so, the per-cpu cache may not be updated. Refer Cache Coherence
- 线程 A 首先进入并获得锁并继续初始化对象。
- 根据 Java 内存模型 (JMM),在初始化 Java 对象之前为变量分配内存并发布。
- 线程 B 进来,由于双重检查锁定,并且变量被初始化,它没有获取锁。
- 这并不能保证对象已初始化,即使如此,每个 CPU 的缓存也可能不会更新。参考缓存一致性
Now coming to the volatile keyword.
现在来到 volatile 关键字。
Volatile variables are always written into the main memory. Hence no cache incoherence.
易失性变量总是写入主内存。因此没有缓存不一致。