我正在查看包含synchronized方法的第三方库中的某些代码,并且在此方法中有一个锁定实例变量的synchronized块。它与此类似:
public class Foo {
final Bar bar = new Bar();
public synchronized void doSomething() {
// do something
synchronized(bar) {
// update bar
}
}
...
}
这有意义吗?如果是这样,在同步方法中有一个同步语句有什么好处?
鉴于同步方法锁定整个对象,对我来说似乎是多余的。在使用非私有的实例变量时,这种方法是否有意义?
答案 0 :(得分:8)
在您的示例中,该方法 锁定Foo
的实例和对象bar
。其他方法可能只锁定对象Foo
上的bar
或的实例。
所以,是的,这取决于他们究竟在做什么。据推测,bar
保护一些较小的数据子集,而某些方法只需锁定bar
即可以线程安全的方式执行其操作。
synchronized
做什么 synchronized
(在方法或语句中)创建一个互斥区域(critical section,或者,特别是对于Java,reentrant mutex)。 "键"一个线程进入临界区是synchronized
语句‡中使用的对象引用。只有一个线程可以(递归地)"拥有"这个"关键"在所有使用相同密钥的块中;也就是说,一次只有一个线程可以在给定的对象引用上输入任何块synchronized
。
这样一个关键部分只是阻止你在块内部执行的操作(变量读/写)与锁定同一对象引用的所有其他关键部分中的任何其他操作同时发生。 (它不会自动保护对象内的所有变量)。
在Java中,这样一个关键部分也会创建happens-before合同。
作为一个有点人为的例子†:
public class Foo {
final Bar bar = new Bar();
private int instanceCounter = 0;
private int barCounter = 0;
public synchronized void incrementBarCounterIfAllowed() {
synchronized (bar) {
if (instanceCounter < 10) barCounter++;
}
}
public synchronized void incrementClassCounter() {
instanceCounter++;
}
public void incrementBarCounter() {
synchronized (bar) {
barCounter++;
}
}
}
实例变量是否为私有对于这种方法是否适用并不重要。在单个类中,您可以拥有多个锁定对象,每个锁定对象都保护自己的数据集。
但是,执行此操作的风险是您必须非常严格遵守编码约定,以通过在不同位置锁定不同顺序的两个锁来防止死锁。例如,如果您从代码中的其他位置执行此操作,请使用上面的代码:
synchronized(myFoo.bar) {
myFoo.incrementClassCounter();
}
使用incrementBarCounterIfAllowed()
方法
† 请注意,barCounter
可能是Bar
等的实例变量 - 我为了简洁起见而避免使用代码示例。
‡对于synchronized
方法,该引用是对类实例的引用(或Class<?>
类的static
方法的引用)。
答案 1 :(得分:2)
当你说“整个对象上的同步方法锁定”时,情况并非如此。仅使用synchronized 意味着线程必须先获取该锁,然后才能输入标记为使用该锁的同步的方法或块。用作实例方法上的同步锁的默认对象是this
。如果将synchronized放在静态方法上,则默认值为this.getClass()
,或者您可以指定要用作锁的对象。使用synchronized不会做任何其他事情,以使实例字段不可访问。
您可以编写一个类,其中一些方法或块受一个锁保护,一些受另一个锁保护,而另一些锁则需要两个锁。确保以相同的顺序获取锁定,否则可能导致死锁。
答案 2 :(得分:1)
考虑一下:
{{1}}
清楚地获取它,如果在 对象上同步2个块,则仅同步块。
答案 3 :(得分:0)
我将举一个真实的例子来解释安迪通过代码解释的内容(对那些发现难以理解的人):
假设您有1 BHK Flat。
在这种情况下,如果有人从后门进入房间并从内部施加锁定。一个人只能进入大厅,不能进入房间,直到房间内的人释放锁。
希望这向那些发现难以理解的人澄清。