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

时间:2012-03-08 17:30:33

标签: java multithreading concurrency thread-safety

我们说我们有非常简单的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. 制作字段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;
       }
    }
    
  3. 使用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;
       }
    }
    
  4. 还有一个事实应该添加到讨论的背景中。在多线程环境中,JVM可以自由地重新排序synchronized块之外的指令,保留逻辑序列,并且在JVM指定的关系之前发生。它可能导致尚未正确构造的发布对象到另一个线程。

    我对第三个案件有几个问题。

    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是否可以重新排序intstructions,从而在字段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;
         }
      }
      
    4. 这个看起来很奇怪的synchronized (new Object())应该可以防止重新排序的影响。它会起作用吗?

      为了清楚起见,所有这些例子都没有任何实际应用。我只是好奇多线程的细微差别。

2 个答案:

答案 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类的调用者来说,这会导致死锁,这一点并不明显。最好将锁定语义保持为您的类内部和私有。