Java同步方法

46

考虑下面的代码:

public synchronized void onSignalsTimeout(List<SignalSpec> specs) {
    if (specs != null && specs.size() > 0) {
        for (SignalSpec spec : specs) {
            ParsedCANSignal timeoutedSignal = new ParsedCANSignal();
            SignalsProvider.getInstance().setSignal(spec.name, spec.parent.parent.channel, timeoutedSignal);
        }
    }
}

我有一个简单的问题: 当线程1调用onSignalsTimeout方法时,线程2能访问在该方法中被访问的对象吗?

无法找到任何地方说明'synchronized'锁只限制对该方法的访问还是对该方法中使用的所有对象的访问。

3个回答

119

首先,忘记同步的方法。所谓的同步方法...

synchronized AnyType foobar(...) {
    doSomething();
}

仅仅是一种简写方式,等同于这样写:

AnyType foobar(...) {
    synchronized(this) {
        doSomething();
    }
}

在任一情况下,方法并没有什么特别之处。真正特别的是同步的,而同步块所做的事情非常简单。当JVM执行以下操作时:

synchronized(foo) {
    doSomething();
}

首先,它会评估表达式foo。结果必须是一个对象引用。然后锁定该对象,执行synchronized块的主体,然后解锁该对象。

但是,“锁定”是什么意思?它可能比您想象的要少。它并不防止其他线程使用对象。它不会防止它们访问对象的字段或更新其字段。锁定对象唯一防止的是,它防止其他线程同时锁定同一个对象。

如果线程A尝试进入synchronized(foo) {...},而线程B已经锁定了foo(无论是在同一个synchronized块中还是在另一个块中),那么线程A将被迫等待,直到线程B释放锁。


您可以使用synchronized块来保护数据。

假设程序具有某些对象的集合,这些对象可以处于不同的状态。假设某些状态是有意义的,但存在其他状态是没有意义的-无效状态。

假设线程在不通过暂时创建无效状态的情况下,无法将数据从一个有效状态更改为另一个有效状态。

如果将更改状态的代码放置在synchronized(foo)块中,并将每个可以查看状态的代码块放置在一个同步块中,该同步块锁定相同的对象foo,则会防止其他线程看到临时的无效状态。


5
您的解释非常清晰易懂。由于您提供了更详细的信息,我必须问一下:当一个线程锁定对象时,其他线程将在哪里被挂起(等待)以获取解锁?我知道这可能是一个“调试”问题,但在我的死锁期间,当我挂起所有线程时感到困惑,因为我发现两个线程都执行着相同的同步方法 - 我本来希望看到其中一个在线程之初执行该方法,另一个则在另一个同步方法的开头等待(以创建循环)。 - arenaq
@arenaq 在哪里? 指的是调试器会显示给你什么?我不知道。我没有在调试器中查看过很多Java代码。我更多地是一个C程序员。在C中,同步不是一种内在的语言特性,所以在C中,调试器会显示线程停在一个同步系统调用中,从一个同步库函数的调用内部,从我的代码的某个地方。我使用过的Java调试器不会显示系统调用或JVM的内部工作原理,而且我也不记得曾经检查过等待获取锁的Java线程的堆栈。 - Solomon Slow
算了吧,这个问题和 Q 无关... 应该开一个新的话题。 - arenaq
1
无关:您有一些内容,我非常支持您努力教育我们正确使用“有意义”的术语! - GhostCat
@Raj,如果有一些数据需要防止多个线程同时访问,请创建一个final Object my_data_lock=new Object(),并编写您的代码,以便只在synchronized(my_data_lock)内部访问数据。由于Java始终只允许一个线程在任何时刻同步给定对象,因此任何时刻只有一个线程将访问数据。 - Solomon Slow
显示剩余2条评论

8
是的,其他线程可以访问方法中使用的对象;同步关键字保证一次只能有一个线程执行方法的代码。
来自https://docs.oracle.com/javase/tutorial/essential/concurrency/syncmeth.html
第一,同一个对象上的两个同步方法不能交错执行。当一个线程为对象执行同步方法时,所有调用该对象的同步方法的其他线程会被阻塞(暂停执行),直到第一个线程完成为止。
第二,在同步方法退出时,它会自动与任何后续调用同一对象的同步方法建立happens-before关系。这确保了对对象状态的更改对所有线程都可见。请注意,构造函数不能同步 - 在构造函数中使用同步关键字是语法错误。同步构造函数没有意义,因为只有创建对象的线程在构造过程中才能访问它。

但是当另一个线程执行该方法时,我仍然可以访问该对象的其他(同步的)方法,对吧? - arenaq
不,正如Bathsheba所写的那样,类中标记为_synchronized_的所有方法都是针对对象实例的监视器,因此当执行其中一个方法时,会获取监视器上的锁定,任何其他线程都必须等待其释放才能执行任何标记为_synchronized_的方法。 - Andrea Iacono
考虑一个对象的实例(我的示例方法来自单例)。该对象具有method1和method2两个方法,均为同步方法。当一个线程在此单例类上调用method1时,另一个线程能否调用method2或至少不是同步的method3? - arenaq
1
当一个线程在此单例类的唯一实例上调用method1时,其他线程无法访问method1和method2,直到第一个线程完成;method3(未同步)可以在任何时候从任何线程访问,即使实例上有锁定活动(这意味着即使另一个线程正在执行任何同步方法)。 - Andrea Iacono

7
在这种情况下,synchronized 同时锁定此方法和在您的类中同样标记为synchronized的任何其他方法。

基本上,如果我有两个同步方法访问一个数据结构,并从一个线程调用一个方法,从第二个线程调用另一个方法,则同步将完全无效。 - arenaq
你可以安排事情,使得数据结构只能通过调用一个被标记为“同步”的函数来访问。 - Bathsheba
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Codebender
你确定吗?如果已经有其他人在执行我不关心的方法,我就不能访问同一个对象的其他方法了吗? - arenaq
没错。像上面这样使用synchronized会锁定this对象的监视器。单独的方法锁定需要不同的技术。 - Bathsheba

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接