与Java中复合语句的复制引用同步

时间:2012-11-17 12:54:23

标签: java synchronization thread-safety

假设我们有一个对象,其方法/字段在“this”上同步。这个问题实际上是关于“这个”,因为我觉得我很难“这个”参考意味着什么。

所以我们的目标是:

class A {
    private Field a;
    private Field b;

    public synchronized void doSomething() {
       //something with a
    }

    public synchronized void somethingElse() {
       //do something as with b
    }

}

然后我们有另一个对象或方法,它通过doSomething和somethingElse方法获取A对象并对a和b进行操作。所以我需要在处理A对象时保持状态一致,因此我进行同步。假设那些A对象是Map的值。然后我迭代价值观并做我做的事情。所以问题是,以下列方式执行它是否是线程安全的:

 for(A aObject : map.values()) {
     synchronized(aObject) {
          aObject.doSomething(); 
          aObject.somethingElse();
     }
 }

如果“this”引用与aObject的引用相同,我想我不应该遇到麻烦。但是如果我这样做会怎么样:

for(A aObject : map.values()) {
      A anotherReference = aObject;

      synchronized(anotherReference) {
         anotherReference.doSomething(); 
         anotherReference.somethingElse();
      }

}

它仍然是线程安全的吗?我的意思是我可以同步锁定参考的本地副本吗?

注意:这是我在代码中需要做的事情的过度简化。

3 个答案:

答案 0 :(得分:2)

同步监视器属于引用的对象,而不是引用,因此您的两个for循环是等效的,它们都在同一对象上同步。

现在是同步方法

public synchronized void foo() {
  // do stuff
}

完全等同于

public void foo() {
  synchronized(this) {
    // do stuff
  }
}

所以在循环中

for(A aObject : map.values()) {
    synchronized(aObject) {
         aObject.doSomething(); 
         aObject.somethingElse();
    }
}

同步块锁定了与doSomething()doSomethingElse()方法使用相同的监视器。你从synchronized块中获得的是,在这两个调用之间,没有其他线程可以潜入并调用同一个A实例上的任何一个方法。

答案 1 :(得分:1)

你似乎对引用的内容感到困惑,所以我会去阅读它们。当您使用同步块时,您不是在引用本身上同步,而是在引用所引用的对象实例上进行同步。

例如:

Object a = new Object();
Object b = a;

synchronized(a) { ... }
synchronized(b) { ... }

这两个同步块在相同的Object实例上进行同步,因为ab引用了相同的Object实例。

接下来,同步方法与同步this引用相同。

例如:

公共类A {   public synchronized void doStomething(){...}   public void doSomethingElse(){synchronized(this){...}} }

这两种方法都使用称为this的自引用在同一个Object实例(当前实例)上进行同步。您可以将其中一个示例重写为另一个示例,它们是等效的。

所以,回到原来的例子,我希望你能理解,当你通过引用(作为我的第一个例子)在外部对象实例上进行同步时,它正在做同样的事情。对象在内部同步

要进行换行,最后一个示例是处理同步集合时的常用习惯用法,因为它使调用者能够确保相对于集合以原子方式执行2个操作。

例如:

// this will result in a List where all methods are internally synchronized
List<Object> syncList = Collections.synchronizedList(new ArrayList<Object>());

// i can safely perform an atomic operation on the List using this pattern
synchronized(syncList) {
  if(syncList.isEmpty()) { // <- synchronized method call
    syncList.add(...); // <- synchronized method call
  }
}

答案 2 :(得分:0)

在Java中,我们有两个基本的同步习语:同步方法同步语句

使用第一个习语(synchronized方法)时,如下面的代码所示:

public class SynchronizedCounter {
    private int c = 0;

    public synchronized void increment() {
        c++;
    }

    public synchronized void decrement() {
        c--;
    }

    public synchronized int value() {
        return c;
    }
}

您有两个主要影响:

1)对同一对象的两个同步方法的调用不可能进行交错。当一个线程正在为对象执行同步方法时,所有其他线程都会调用同一对象的同步方法阻塞(暂停执行),直到第一个线程完成对象为止。

2)当synchronized方法退出时,它会自动建立一个before-before关系,以及后续调用同一对象的synchronized方法。这可以保证对所有线程都可以看到对象状态的更改。

创建同步代码的另一种方法是使用 synchronized语句。与synchronized方法不同,synchronized语句必须指定提供内部锁的对象:

public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}

在你的代码中,你正在使用这两种习语。然后,您的第一个for循环不需要 synchronized(aObject),因为您的类方法已经是同步方法。

来源:http://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html

但是让我们说你的班级方法没有同步。你的第二个代码示例:

for(A aObject : map.values()) {
      A anotherReference = aObject;

      synchronized(anotherReference) {
         anotherReference.doSomething(); 
         anotherReference.somethingElse();
      }

}

仍然有效,因为在Java中,每个对象都有一个与之关联的内在锁。当你调用synchronized(Object o)时,你正在获取与Object:anotherReference相关联的锁,在你的情况下是aObject。

让我们考虑两个线程:T1和T2。 如果T1在T2之前调用this for循环,它将获取与aObject关联的内部锁,并且T2将无法执行相同操作,直到T1将结束两个方法:doSomenthing()和somethinElse()。