假设我们有一个对象,其方法/字段在“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();
}
}
它仍然是线程安全的吗?我的意思是我可以同步锁定参考的本地副本吗?
注意:这是我在代码中需要做的事情的过度简化。
答案 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实例上进行同步,因为a
和b
引用了相同的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()。