以下 java 代码在没有 volatile 的情况下是线程安全的吗?
Is the following java code thread safe without volatile?
public static Singleton singleton;
public static Singleton get(){
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
有人说 singleton
变量没有 volatile 是错误的。但我认为这是创建单例对象的正确代码。我想知道这段代码有没有线程安全?
volatile
不需要 code.but 中的变量,您的代码存在一些性能缺陷。每次 get
中的代码都会 运行 在 synchronized
块中,这几乎不会造成性能开销。您可以使用双重检查锁定机制来避免性能开销。下面的代码显示了使用双重检查锁定机制在 java 中创建线程安全单例的正确方法。
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;
}
}
有关更多详细信息,请访问此 link 并滚动到底部部分“使用 Volatile 修复双重检查锁定”。
正如 anatolyg 指出的那样,您应该将字段 singleton
设为私有,以避免对该字段进行不必要的非线程安全访问。
此外,即使在:
public static Singleton get(){
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
return singleton;
在 synchronized
块之外,这段代码仍然是线程安全的 因为其余代码在 synchronized
内部] 块,因此,该块内的所有线程将强制执行 happens-before 关系(即, 线程不可能 return null 如果实例设置正确)。
话虽这么说,但请注意:引用 Holger
As long as the actual write to singleton happens-before the beginning
of the synchronized block, everything works. It only works
because there is at most one write, definitely performed before the
return. If more writes were possible, they could happen concurrently
to the return statement that is outside the synchronized block.
有一个完整的 解决了为什么将 return singleton
留在 synchronized
块之外是线程安全的。
尽管如此,我也同意其他用户的相同意见,例如
since the return doesn't take any CPU or anything, there is no reason
why it shouldn't be inside of the synchronized block. If it was then
the method can be marked as synchronized if we are in the Singleton
class here. This would be cleaner and better in case singleton gets
modified elsewhere.
也就是说您不需要 volatile 子句,因为您正在同步变量 singleton
的初始化。在这种情况下,synchronized
子句不仅保证多个线程不会访问:
if (singleton == null) {
singleton = new Singleton();
}
而且每个线程都能看到 singleton
字段的最新引用。因此,多个线程将不同的对象实例分配给singleton
字段的race-condition将不会发生。
Some one say that no volatile for singleton variable is wrong.
可能此人将您的代码误认为是 double-checked locking pattern, which is a performance optimization over the version that you have shown. In your version threads will synchronize every time they call the method get
, which is not necessary after the variable singleton
has been correctly initialized. This is the overhead that the double-checked locking pattern tries to avoid. To achieve that the volatile is needed (you can read an in-depth explanation on this SO Thread), more information about this double-checked locking pattern can be found here。
差不多。如果您使用 synchronized
块对 variable/field 进行线程安全访问,如果您在同一锁下进行读取和写入(在同一对象的监视器上同步),则代码是正确的。因此,在您的代码中,您应该防止由“private”修饰符而不是“public”声明正常读取(没有任何内存障碍)。
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
}
您可能会注意到最后一次读取 return singleton;
是正常的,但它是在第一次同步读取(如果单例不为空)或写入(如果为空)和不需要放在同步块内,因为一个线程的 PO->HB (https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5
“如果 x 和 y 是同一线程的操作,并且 x 在程序顺序中出现在 y 之前,则 hb(x, y)”)。
但从我的角度来看,以下结构似乎更惯用
// 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;
}
现在代码是正确的,但可能效率不高。这将我们带到了 Double-Checked Locking 习语。您可以在
https://shipilev.net/blog/2014/safe-public-construction/
顺便说一句,有时甚至下面的代码也可以:
private static volatile Singleton singleton;
public static Singleton get() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
此代码没有数据竞争,但有竞争条件。它总是 return 一个 Singleton 的实例,但并不总是相同的。换句话说,在 get()
的第一次调用中,我们可能会看到 Singleton 的不同实例 return 返回,它们将彼此重写到 singleton
字段,但如果您不这样做小心 :)...(例如,具有相同状态的小型 immutable/read-only 单例)
public static Singleton singleton;
public static Singleton get(){
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
有人说 singleton
变量没有 volatile 是错误的。但我认为这是创建单例对象的正确代码。我想知道这段代码有没有线程安全?
volatile
不需要 code.but 中的变量,您的代码存在一些性能缺陷。每次 get
中的代码都会 运行 在 synchronized
块中,这几乎不会造成性能开销。您可以使用双重检查锁定机制来避免性能开销。下面的代码显示了使用双重检查锁定机制在 java 中创建线程安全单例的正确方法。
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;
}
}
有关更多详细信息,请访问此 link 并滚动到底部部分“使用 Volatile 修复双重检查锁定”。
正如 anatolyg 指出的那样,您应该将字段 singleton
设为私有,以避免对该字段进行不必要的非线程安全访问。
此外,即使在:
public static Singleton get(){
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
return singleton;
}
return singleton;
在 synchronized
块之外,这段代码仍然是线程安全的 因为其余代码在 synchronized
内部] 块,因此,该块内的所有线程将强制执行 happens-before 关系(即, 线程不可能 return null 如果实例设置正确)。
话虽这么说,但请注意:引用 Holger
As long as the actual write to singleton happens-before the beginning of the synchronized block, everything works. It only works because there is at most one write, definitely performed before the return. If more writes were possible, they could happen concurrently to the return statement that is outside the synchronized block.
有一个完整的 return singleton
留在 synchronized
块之外是线程安全的。
尽管如此,我也同意其他用户的相同意见,例如
since the return doesn't take any CPU or anything, there is no reason why it shouldn't be inside of the synchronized block. If it was then the method can be marked as synchronized if we are in the Singleton class here. This would be cleaner and better in case singleton gets modified elsewhere.
也就是说您不需要 volatile 子句,因为您正在同步变量 singleton
的初始化。在这种情况下,synchronized
子句不仅保证多个线程不会访问:
if (singleton == null) {
singleton = new Singleton();
}
而且每个线程都能看到 singleton
字段的最新引用。因此,多个线程将不同的对象实例分配给singleton
字段的race-condition将不会发生。
Some one say that no volatile for singleton variable is wrong.
可能此人将您的代码误认为是 double-checked locking pattern, which is a performance optimization over the version that you have shown. In your version threads will synchronize every time they call the method get
, which is not necessary after the variable singleton
has been correctly initialized. This is the overhead that the double-checked locking pattern tries to avoid. To achieve that the volatile is needed (you can read an in-depth explanation on this SO Thread), more information about this double-checked locking pattern can be found here。
差不多。如果您使用 synchronized
块对 variable/field 进行线程安全访问,如果您在同一锁下进行读取和写入(在同一对象的监视器上同步),则代码是正确的。因此,在您的代码中,您应该防止由“private”修饰符而不是“public”声明正常读取(没有任何内存障碍)。
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
}
您可能会注意到最后一次读取 return singleton;
是正常的,但它是在第一次同步读取(如果单例不为空)或写入(如果为空)和不需要放在同步块内,因为一个线程的 PO->HB (https://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4.5
“如果 x 和 y 是同一线程的操作,并且 x 在程序顺序中出现在 y 之前,则 hb(x, y)”)。
但从我的角度来看,以下结构似乎更惯用
// 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;
}
现在代码是正确的,但可能效率不高。这将我们带到了 Double-Checked Locking 习语。您可以在 https://shipilev.net/blog/2014/safe-public-construction/
顺便说一句,有时甚至下面的代码也可以:
private static volatile Singleton singleton;
public static Singleton get() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
此代码没有数据竞争,但有竞争条件。它总是 return 一个 Singleton 的实例,但并不总是相同的。换句话说,在 get()
的第一次调用中,我们可能会看到 Singleton 的不同实例 return 返回,它们将彼此重写到 singleton
字段,但如果您不这样做小心 :)...(例如,具有相同状态的小型 immutable/read-only 单例)