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



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

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

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

同步监视器属于被引用的对象,而不是引用,因此您的两个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();
}
}

同步块正在锁定与CCD_ 2和CCD_。从synchronized块中获得的好处是,在这两个调用之间,没有其他线程可以偷偷进入并调用同一个A实例上的任何一个方法。

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

例如:

Object a = new Object();
Object b = a;
synchronized(a) { ... }
synchronized(b) { ... }

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

此后,同步方法与在this引用上同步相同。

例如:

公共A类{公共同步的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
}
}

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

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

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结束两个方法:doSomething()和something Else()。

最新更新