请澄清我对单身和多线程的疑问:
getInstance()
时会发生什么
方法同时?getInstance()
synchronized
吗?答案 0 :(得分:196)
是的,这是必要的。您可以使用多种方法通过延迟初始化来实现线程安全:
严酷的同步:
private static YourObject instance;
public static synchronized YourObject getInstance() {
if (instance == null) {
instance = new YourObject();
}
return instance;
}
此解决方案要求每个线程同步,而实际上只需要前几个。
private static final Object lock = new Object();
private static volatile YourObject instance;
public static YourObject getInstance() {
YourObject r = instance;
if (r == null) {
synchronized (lock) { // While we were waiting for the lock, another
r = instance; // thread may have instantiated the object.
if (r == null) {
r = new YourObject();
instance = r;
}
}
}
return r;
}
此解决方案确保只有尝试获取单例的前几个线程必须完成获取锁定的过程。
private static class InstanceHolder {
private static final YourObject instance = new YourObject();
}
public static YourObject getInstance() {
return InstanceHolder.instance;
}
此解决方案利用Java内存模型保证类初始化以确保线程安全。每个类只能加载一次,只有在需要时才会加载。这意味着第一次调用getInstance
时,将加载InstanceHolder
并创建instance
,并且由于这是由ClassLoader
控制的,因此无需进行其他同步
答案 1 :(得分:63)
此模式对实例执行线程安全的延迟初始化,而不进行显式同步!
public class MySingleton {
private static class Loader {
static final MySingleton INSTANCE = new MySingleton();
}
private MySingleton () {}
public static MySingleton getInstance() {
return Loader.INSTANCE;
}
}
它的工作原理是因为它使用类加载器为您免费执行所有同步:首先在MySingleton.Loader
方法中访问类getInstance()
,因此Loader
类在何时加载第一次调用getInstance()
。此外,类加载器保证在您访问类之前完成所有静态初始化 - 这就是为您提供线程安全性的。
这就像魔术。
它实际上非常类似于Jhurtado的枚举模式,但我发现枚举模式是对枚举概念的滥用(虽然它确实有效)
答案 2 :(得分:22)
如果您正在使用Java中的多线程环境并且需要保证所有这些线程都在访问类的单个实例,则可以使用Enum。这将有助于您处理序列化的附加优势。
public enum Singleton {
SINGLE;
public void myMethod(){
}
}
然后让你的线程使用你的实例,如:
Singleton.SINGLE.myMethod();
答案 3 :(得分:8)
是的,您需要同步getInstance()
。如果不是,可能会出现可以制作类的多个实例的情况。
考虑你有两个线程同时调用getInstance()
的情况。现在假设T1执行刚好超过instance == null
检查,然后T2运行。此时,实例未创建或设置,因此T2将通过检查并创建实例。现在假设执行切换回T1。现在创建了单例,但是T1已经完成了检查!它将继续制作该对象!使getInstance()
同步可以防止出现此问题。
有几种方法可以使单例线程安全,但使getInstance()
同步可能是最简单的。
答案 4 :(得分:6)
Enum singleton
实现线程安全的Singleton的最简单方法是使用Enum
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
System.out.println("This is a singleton");
}
}
自从Java 1.5中引入Enum以来,此代码一直有效。
双重检查锁定
如果你想编写一个在多线程环境中工作的“经典”单例(从Java 1.5开始),你应该使用这个。
public class Singleton {
private static volatile Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class){
if (instance == null) {
instance = new Singleton();
}
}
}
return instance ;
}
}
这在1.5之前不是线程安全的,因为volatile关键字的实现是不同的。
早期加载Singleton(甚至在Java 1.5之前工作)
此实现在加载类时实例化单例并提供线程安全性。
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
public void doSomething(){
System.out.println("This is a singleton");
}
}
答案 5 :(得分:2)
您还可以使用静态代码块在类加载时实例化实例,并防止线程同步问题。
public class MySingleton {
private static final MySingleton instance;
static {
instance = new MySingleton();
}
private MySingleton() {
}
public static MySingleton getInstance() {
return instance;
}
}
答案 6 :(得分:0)
在多线程环境中,在Java中实现Singleton的最佳方法是什么?
请参阅此文章,了解实施Singleton的最佳方法。
What is an efficient way to implement a singleton pattern in Java?
当多个线程同时尝试访问getInstance()方法时会发生什么?
这取决于你实现方法的方式。如果你使用不带volatile变量的双重锁定,你可能会获得部分构造的Singleton对象。
有关详细信息,请参阅此问题:
Why is volatile used in this example of double checked locking
我们可以让singleton的getInstance()同步吗?
使用Singleton类时确实需要同步吗?
如果以下列方式实施Singleton,则不需要
更多详情请参阅此问题
答案 7 :(得分:0)
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis () {...}
}
来源:有效的Java - >第2项
它建议使用它,如果你确定该类将始终保持单身。