任何人都可以解释下面的代码如何在多线程环境中正常工作,尤其是当它不使用synchronized关键字时?
public class Singleton {
private Singleton() {}
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
答案 0 :(得分:1)
非同步单例可以在线程环境中正常工作,前提是它们在其他线程尝试使用它们之前在一个线程中实例化。
这可能就像在启动任何其他线程之前从主线程调用getInstance()
一样简单。
但是,在这种特殊情况下,这是无关紧要的。由于您的实例变量是static final
,这意味着它将在最初加载类时构造。通过调用getInstance()
,类加载器会引入类,并且作为其中的一部分,在允许调用INSTANCE
之前构建getInstance()
成员。
类加载器本身具有锁定机制以防止多个线程并发执行,因此对getInstance()
的所有调用(包括第一个,在加载类之后立即执行)将返回已经初始化的值。
答案 1 :(得分:0)
对于前期初始化,不需要使用synchronized关键字,因为您静态初始化实例,几乎在每种情况下都是首选的方法,原因有两个。您可以在应用程序运行时间长度内分摊昂贵的初始化成本,其中两个(对您而言尤其重要)是它无需同步,从而避免了毛发双重检查锁定方案。
如果选择使用延迟初始化,则需要确保整个初始化过程已同步,否则在多处理器环境中存在创建两个单例实例的风险。 Double Checked Locking And Why its a problem
初始化后,您的代码将在多线程环境中正常运行。需要注意的是,您在所有线程中共享一个实例,因此您需要确保INSTANCE
是线程安全的。有两种方法可以做到这一点。首先,您可以确保所有成员都是私有的,并且对更改INSTANCE's
状态的方法的任何访问都使用synchronized
或包含发生状态修改的同步块。
实现线程安全的第二种可能更好的方法是预先初始化您的实例,并在初始化后使其变为不可变。不可变对象可以在线程之间自由共享而不需要同步。阅读本Java实践文章,了解有关不可变对象的更多信息以及如何创建它们。