0

假设我们有一个对象,其方法/字段在“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
    }

}

然后我们有另一个对象或方法,它接受 A 对象并通过 doSomething 和 somethingElse 方法在 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();
      }

}

它仍然是线程安全的吗?我的意思是我可以在锁定引用的本地副本上同步吗?

注意:这是对我需要在代码中执行的操作的过度简化。

4

3 回答 3

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()方法使用自己的监视器相同的监视器。您从同步块中获得的好处是,没有其他线程可以在这两个调用之间潜入并在同一个 A 实例上调用这些方法中的任何一个。

于 2012-11-17T13:25:14.087 回答
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) { ... } } }

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

所以,回到你原来的例子,我希望你会明白,当你通过引用在外部同步一个 Object 实例时(作为我的第一个例子),它做的事情与一个在内部同步的 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
  }
}
于 2012-11-17T13:32:47.583 回答
0

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

当您使用第一个习语(同步方法)时,如下代码所示:

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)当一个同步方法退出时,它会自动建立一个happens-before关系与任何后续对同一对象的同步方法的调用。这保证了对象状态的更改对所有线程都是可见的。

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

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 之前调用这个 for 循环,它会获取与 aObject 关联的内在锁,并且 T2 将无法执行相同操作,直到 T1 结束这两个方法:doSomenthing() 和 somethinElse()。

于 2012-11-17T13:07:02.520 回答