以下java代码在没有volatile的情况下是线程安全的吗?
有人说,以下java代码在没有volatile的情况下是线程安全的吗?,java,multithreading,parallel-processing,synchronization,volatile,Java,Multithreading,Parallel Processing,Synchronization,Volatile,有人说,singleton变量的novolatile是错误的。但我认为这是创建单例对象的正确代码。我想知道此代码是否具有线程安全性?您的代码中的变量不需要volatile。但是您的代码有一些性能缺陷。每次get中的代码都会在synchronized块中运行,这几乎不会造成性能开销。您可以使用双重检查锁定机制来避免性能开销。下面的代码展示了使用双重检查锁定机制在java中创建线程安全单例的正确方法 public static Singleton singleton; public static
singleton
变量的novolatile是错误的。但我认为这是创建单例对象的正确代码。我想知道此代码是否具有线程安全性?您的代码中的变量不需要volatile
。但是您的代码有一些性能缺陷。每次get
中的代码都会在synchronized
块中运行,这几乎不会造成性能开销。您可以使用双重检查锁定机制来避免性能开销。下面的代码展示了使用双重检查锁定机制在java中创建线程安全单例的正确方法
public static Singleton singleton;
public static Singleton get(){
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
有关更多详细信息,请访问此部分并滚动到底部部分“使用Volatile修复双重检查锁定”。如前所述,您应该将字段设置为私有,以避免对该字段进行不必要的非线程安全访问
此外,即使在:
class Singleton {
private static volatile Singleton singleton = null;
public static Singleton get() {
if (singleton == null) {
synchronized(this) {
if (singleton == null)
singleton = new Singleton();
}
}
return singleton;
}
}
返回单例代码>在synchronized
块之外,此代码仍然是线程安全的,因为剩余的代码在synchronized
块内,因此,该块内的所有线程都将强制使用“发生在之前”关系(即,如果实例设置正确,线程将无法返回null)
要注意:引用
只要对singleton的实际写入发生在开始之前
在同步块中,一切正常它只起作用
因为最多有一次写入,所以在
返回。如果有可能进行更多的写操作,它们可能同时发生
指向同步块外部的返回语句
有一个完整的解决方案可以解决为什么在synchronized
块之外保留返回单例
是线程安全的
尽管如此,我同意其他用户的相同观点,例如
因为返回不需要任何CPU或任何东西,所以没有理由
为什么它不应该在同步块内。如果是那时
如果我们在单例中,该方法可以标记为synchronized
在这里上课。这将是更干净,更好的情况下,单身得到
在别处修改
也就是说,您不需要volatile子句,因为您正在同步变量singleton
的初始化。在此上下文中,synchronized
子句不仅保证多个线程不会访问:
public static Singleton get(){
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
而且每个线程都可以看到单例
字段的最新引用。因此,将不同对象实例分配给singleton
字段的多线程的争用条件将不会发生
有人说单例变量的no volatile是错误的
这个人可能误解了您的代码,这是对您所展示版本的性能优化。在您的版本中,每次调用方法get
,线程都会同步,在变量singleton
正确初始化后,这是不必要的。这是双重检查锁定模式试图避免的开销。为了实现需要volatile(您可以阅读关于这方面的深入解释),可以找到有关这种双重检查锁定模式的更多信息。。如果使用synchronized
块对变量/字段进行线程安全访问,则如果在同一锁下执行读写操作(在同一对象的监视器上进行同步),则代码是正确的。因此,在代码中,您应该防止由“private”修饰符而不是“public”声明读取正常(没有任何内存障碍)
if (singleton == null) {
singleton = new Singleton();
}
您可能会注意到,最后一次读取返回单例
是正常的,但它是在第一次同步读取(如果singleton不为null)或写入(如果为null)之后的后续读取(按程序顺序),并且不需要放置在同步块中,因为一个线程的PO->HB(
“如果x和y是同一线程的操作,并且x在程序顺序中位于y之前,那么hb(x,y)”)
但在我看来,以下结构似乎更为惯用
private static Singleton singleton; // now we don't have direct access (to read/write) the field outside
public static Singleton get(){
synchronized (Singleton.class) { // all reads in the synchronized Happens-Before all writes
if (singleton == null) { // first read
singleton = new Singleton(); // write
}
}
return singleton; // the last normal read
}
或者只是
// all access is under the lock
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
现在代码是正确的,但可能效率不高。这就引出了这个成语。您可以在中找到对该问题的良好回顾
顺便说一句,有时甚至以下代码也可以:
public static synchronized Singleton get() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
此代码没有数据竞争,而是竞争条件。它总是返回一个Singleton实例,但不总是相同的实例。换句话说,在第一次调用get()
时,我们可能会看到返回的Singleton的不同实例,这些实例会相互重写到Singleton
字段,但如果您不介意的话:)。。。(例如,具有相同状态的小型不可变/只读单例)不清楚为什么您认为他们的代码可以生成两个实例。此代码已正确同步。因此,它将很好地工作;但是,如果您不想在每次获得值时为进入锁付出代价,则有性能更好的替代方案。这就是双重检查锁定问题产生的原因,然后您很快就会得到一个volatile。
private static volatile Singleton singleton;
public static Singleton get() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}