Java 中的线程安全类,通过同步块

假设我们有非常简单的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 类:

  1. 让它真正不可变

    public class MyClass {
       private final int number;
    
       public MyClass(int number) {
        this.number = number;
       }
    
       public int getNumber() {
        return number;
       }
    
    }
    
  2. 使字段 。numbervolatile

    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;
       }
    }
    
  3. 使用块。在实践中,Java 并发的第 4.3.5 章中描述的此方法的经典版本。有趣的是,它在本书的勘误表中提到了一个错误。synchronized

    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 可以自由地对块外的指令进行重新排序,从而保留 JVM 指定的逻辑序列和发生前关系。它可能会导致尚未正确构造的发布对象到另一个线程。synchronized

关于第三种情况,我有几个问题。

  1. 它是否等效于以下代码段:

    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;
       }
    }
    
  2. 在第三种情况下,是否会阻止重新排序,或者JVM可以对指令进行重新排序,从而在字段中发布具有默认值的对象?number

  3. 如果第二个问题的答案是肯定的,那么我还有一个问题。

     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())

需要明确的是,所有这些例子都没有任何实际应用。我只是对多线程的细微差别感到好奇。


答案 1

synchronized(new Object())将不执行任何操作,因为同步仅在您同步的对象上。因此,如果线程 A 同步于 ,而线程 B 同步于 ,则它们之间没有发生之前。由于我们可以知道一个事实,即没有其他线程会在您在那里创建的线程上进行同步,因此这不会在任何其他线程之间建立之前发生。oneObjectanotherObjectnew Object()

关于构造函数中的你的,如果你的对象安全地发布到另一个线程,你不需要它;如果不是这样,你可能会陷入一团糟的麻烦。我刚才在并发利益列表中问了这个问题,结果出现了一个有趣的线程。请特别看到这封电子邮件,其中指出,即使同步了构造函数,在没有安全发布的情况下,另一个线程也可以在您的字段中看到默认值,而这封电子邮件(恕我直言)将整个事情联系在一起。synchronzied


答案 2

在问题#3中,是一个无操作,不会阻止任何东西。编译器可以确定没有其他线程可以同步该对象(因为没有其他线程可以访问该对象)。这是Brian Goetz的论文“Java理论与实践:野马中的同步优化”中的一个明确例子。synchronized(new Object())

即使您确实需要在构造函数中进行同步,即使您的块很有用 - 即,您正在同步不同的长期存在对象,因为您的其他方法正在同步 ,如果您不在同一变量上进行同步,您也会遇到可见性问题。也就是说,您确实希望构造函数也使用 。synchronized(new Object())thissynchronized(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


推荐