我们说我们有非常简单的Java类MyClass
。
public class MyClass {
private int number;
public MyClass(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
构造具有某种状态的线程安全Java类有三种方法:
让它真正不变的
public class MyClass {
private final int number;
public MyClass(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
}
制作字段number
volatile
。
public class MyClass {
private volatile int number;
public MyClass(int number) {
this.number = number;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
使用synchronized
块。这种方法的经典版本在实践中的Java Concurrency第4.3.5章中描述。有趣的是,它在本书的勘误表中提到的示例中有错误。
public class MyClass {
private int number;
public MyClass(int number) {
setNumber(number);
}
public synchronized int getNumber() {
return number;
}
public synchronized void setNumber(int number) {
this.number = number;
}
}
还有一个事实应该添加到讨论的背景中。在多线程环境中,JVM可以自由地重新排序synchronized
块之外的指令,保留逻辑序列,并且在JVM指定的关系之前发生。它可能导致尚未正确构造的发布对象到另一个线程。
我对第三个案件有几个问题。
它是否等同于以下代码:
public class MyClass {
private int number;
public MyClass(int number) {
synchronized (this){
this.number = number;
}
}
public synchronized int getNumber() {
return number;
}
public synchronized void setNumber(int number) {
this.number = number;
}
}
在第三种情况下是否会阻止重新排序,或者JVM是否可以重新排序intstructions,从而在字段number
中发布具有默认值的对象?
如果第二个问题的答案是肯定的,那么我还有一个问题。
public class MyClass {
private int number;
public MyClass(int number) {
synchronized (new Object()){
this.number = number;
}
}
public synchronized int getNumber() {
return number;
}
public synchronized void setNumber(int number) {
this.number = number;
}
}
这个看起来很奇怪的synchronized (new Object())
应该可以防止重新排序的影响。它会起作用吗?
为了清楚起见,所有这些例子都没有任何实际应用。我只是好奇多线程的细微差别。
答案 0 :(得分:8)
synchronized(new Object())
将不执行任何操作,因为同步仅适用于您同步的对象。因此,如果线程A在oneObject
上同步,并且线程B在anotherObject
上同步,则它们之间不会发生。因为我们可以知道没有其他线程会在你创建的new Object()
上同步,所以这不会在任何其他线程之间建立先发生过。
关于构造函数中的synchronzied
,如果您的对象安全地发布到另一个线程,则不需要它;如果不是这样的话,你可能会遇到麻烦。我刚刚在并发兴趣列表上问了这个问题,an interesting thread resulted。特别参见this email,它指出即使您的构造函数已同步,在没有安全发布的情况下,另一个线程可以在您的字段中看到默认值,this email哪个(imho)将整个事物联系在一起
答案 1 :(得分:2)
在问题#3中,synchronized(new Object())
是无操作,并且不会阻止任何操作。编译器可以确定没有其他线程可能在该对象上进行同步(因为没有其他线程可以访问该对象。)这是Brian Goetz的论文“Java theory and practice: Synchronization optimizations in Mustang”中的一个明确示例。
即使您确实需要在构造函数中进行同步,即使您的synchronized(new Object())
块 有用 - 也就是说,您正在同步一个不同的长期对象,因为您的另一个方法正在this
上进行同步,如果您没有在同一个变量上进行同步,则会出现可见性问题。也就是说,您确实希望构造函数也使用synchronized(this)
。
旁白:
this
上的同步被视为不良格式。相反,在某些私有最终字段上进行同步。调用者可能会在您的对象上进行同步,这可能会导致死锁。请考虑以下事项:
public class Foo
{
private int value;
public synchronized int getValue() { return value; }
public synchronized void setValue(int value) { this.value = value; }
}
public class Bar
{
public static void deadlock()
{
final Foo foo = new Foo();
synchronized(foo)
{
Thread t = new Thread() { public void run() { foo.setValue(1); } };
t.start();
t.join();
}
}
}
对Foo
类的调用者来说,这会导致死锁,这一点并不明显。最好将锁定语义保持为您的类内部和私有。