Java 同步方法中的同步块

Java 同步方法中的同步块,java,multithreading,concurrency,synchronization,Java,Multithreading,Concurrency,Synchronization,我正在查看第三方库中包含同步方法的一些代码,在该方法中有一个锁定实例变量的同步块。与此类似: public class Foo { final Bar bar = new Bar(); public synchronized void doSomething() { // do something synchronized(bar) { // update bar } } ... } 这有意义吗?如果是这

我正在查看第三方库中包含同步方法的一些代码,在该方法中有一个锁定实例变量的同步块。与此类似:

public class Foo {
   final Bar bar = new Bar();

   public synchronized void doSomething() {
       // do something
       synchronized(bar) {
           // update bar
       }
   }
   ...
}
这有意义吗?如果是这样,在同步方法中使用同步语句有什么好处


考虑到同步方法锁定整个对象,我觉得它是多余的。当使用非私有的实例变量时,这种方法可能有意义?

在您的示例中,该方法既锁定
Foo
的实例,又锁定对象
bar
。其他方法可能仅锁定
Foo
的实例或对象

所以,是的,这完全取决于他们在做什么。大概
bar
可以保护一些较小的数据子集,有些方法只需锁定
bar
即可以线程安全的方式执行其操作

synchronized
的作用是什么
synchronized
(在方法或语句中)创建互斥区(或者,特别是对于Java,创建互斥区)。线程进入临界区的“键”是
synchronized
语句中使用的对象引用。在使用同一个键的所有块中,一次只能有一个线程(递归地)“拥有”这个“键”;也就是说,一次只有一个线程可以进入给定对象引用上的任何块
synchronized

这样的关键部分只是防止在块内执行的操作(变量读/写)与锁定同一对象引用的所有其他关键部分中的任何其他操作同时发生。(它不会自动保护对象内的所有变量)

在Java中,这样的关键部分也会创建一个契约

以身作则 作为一个有点做作的例子†:

使用
incrementBarCounterIfAllowed()
方法可能会出现死锁

†请注意,
barCounter
可能是
Bar
等的实例变量-为了代码示例的简洁性,我避免了这一点

‡对于
同步
方法,该引用是对类实例的引用(对于
静态
方法的类,该引用是对
的引用)

public void someMethod() {

    synchronized(bar) {
       // fully accessible before entering the other synchronized bar block
       // but not afterwards 
    }

}
明白了,如果两个块在相同的对象上同步,则只同步块。

当你说“同步方法锁定整个对象”时,那不是真的。仅使用synchronized意味着线程必须先获取该锁,然后才能进入使用该锁的标记为synchronized的方法或块。用作同步实例方法锁的默认对象是
this
。如果将synchronized放在静态方法上,则默认值为
this.getClass()
,或者可以指定要用作锁的对象。使用synchronized只会使实例字段不可访问


您可以编写一个类,其中一些方法或块由一个锁保护,一些方法或块由另一个锁保护,而对于其他方法或块,您需要两个锁。确保以相同的顺序获取锁,否则可能会导致死锁。

我将给出一个真实的示例来解释Andy通过代码解释的内容(对于那些觉得难以理解的人):

假设你有一套1 BHK的公寓

  • 进入房间的方式是通过大厅(你必须先进入大厅才能进入房间)
  • 你想限制某人使用你的房间,但他可以使用/进入大厅
  • 在这种情况下,如果有人从后门进入房间,并从里面上锁。一个人只能进入大厅,在房间里的人打开锁之前不能进入房间


    希望这能让那些难以理解的人明白这一点。

    我相信,如果同步块的监视器对象是“this”而不是“bar”,那么它将是多余的。谢谢回复。正如您所说,每个锁都保护自己的数据集。我认为当一个对象被锁定时,它的所有实例变量都会受到保护。“我认为情况并非如此?”吉尔伯塔雷斯匕首说——希望我的最新答案能解决这个问题。锁是一个互斥区域,根据传递给
    synchronize
    的对象的引用进行协调。它保护您在锁内选择执行的任何操作。这取决于您是否始终使用相同的锁保护相同的数据。e、 g.
    public void breakMe(){instanceCounter++;}
    可以添加到上述示例中,并且
    instanceCounter
    将不再是线程安全的。@TheGilbertArenasDagger是-除非某个外部类决定要对
    bar
    的实例执行并发操作,这将与您在
    Foo
    中执行的操作发生冲突(尽管这将是一个非常糟糕的并行设计)。@gilbertarenasdagger,“锁定”对象(即在其上同步)不会阻止其他线程修改该对象。它只防止其他线程同时“锁定”同一对象。实际上,如果您有两个线程,它们不会同时在
    synchronized(bar)
    块中共存,因此单个“synchronized(bar)”块也会阻止同一块上的其他线程。此外,除了您的语句之外,“synchronized(bar)”在一个线程离开另一个“synchronized(bar)”块后是完全可访问的,而您的注释可能被解释为意味着它在之后“永远”是不可访问的。
    synchronized(myFoo.bar) {
      myFoo.incrementClassCounter();
    }
    
    public void someMethod() {
    
        synchronized(bar) {
           // fully accessible before entering the other synchronized bar block
           // but not afterwards 
        }
    
    }