【问题标题】:synchronized section does not block!同步部分不阻塞!
【发布时间】:2010-07-13 09:03:34
【问题描述】:

我昨天注意到了一件很奇怪的事情。似乎两个线程同时进入了两个同步块,锁定在同一个对象上。

包含相关代码的类 (MyClass) 如下所示:

private static int[]    myLock  = new int[0];

protected static int methodA(final long handle, final byte[] sort) {
    synchronized (myLock) {
        return xsMethodA(handle, sort);
    }
}

protected static int methodB(final long handle) {
    synchronized (myLock) {
        return xsMethodB(handle);
    }
}

我创建了一个运行上述类的应用程序的线程转储,当我看到这个时非常惊讶:

"http-8080-136" daemon prio=10 tid=0x00000000447df000 nid=0x70ed waiting for monitor entry [0x00007fd862aea000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.MyClass.methodA(MyClass.java:750)
    - locked <0x00007fd8a6b8c790> (a [I)
    at com.SomeOtherClass.otherMethod(SomeOtherClass.java:226)
    ...

"http-8080-111" daemon prio=10 tid=0x00007fd87d1a0000 nid=0x70c8 waiting for monitor entry [0x00007fd86e15f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.MyClass.methodB(MyClass.java:991)
    - locked <0x00007fd8a6b8c790> (a [I)
    at com.SomeOtherClass.yetAnotherMethod(SomeOtherClass.java:3231)
    ...

(为了简单起见,我更改了类和方法名称,所以不要被这些愚蠢的名称所迷惑。)

似乎线程 http-8080-136 和 http-8080-111 都获得了myLock 的锁定。它是同一个对象,因为对象地址是相同的:0x00007fd8a6b8c790。 Java 运行时规范说明了 synchronized 关键字:

同步语句代表正在执行的线程获取互斥锁(第 17.1 节),执行一个块,然后释放锁。当执行线程拥有锁时,没有其他线程可以获取锁。 [The Java Language Specification, 14.19]

那么这怎么可能呢?

线程转储中还有另外 44 个线程“等待”锁定。这就是线程正在等待时的样子:

"http-8080-146" daemon prio=10 tid=0x00007fd786dab000 nid=0x184b waiting for monitor entry [0x00007fd8393b6000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at com.MyClass.methodC(MyClass.java:750)
    - waiting to lock <0x00007fd8a6b8c790> (a [I)
    at com.SomeOtherClass.yetAnoterMethod2(SomeOtherClass.java:226)

【问题讨论】:

    标签: java concurrency multithreading synchronized


    【解决方案1】:

    我在热点开发邮件列表上提出了同样的问题,并从 Christopher Phillips 那里得到了非常好的回答:


    你好爱德华

    我认为它的线程转储具有误导性。

    如果您真的认为 2 个同时处于锁定状态,您可能应该得到一个 gcore(这是外部一致的)。

    您看到“等待监视器进入”的状态实际上是 MONITOR_WAIT ,它可以表示在实际获取热锁之前的以下代码: (另见 osThread.hpp 中的 OSThreadContendState)调用自: src/share/vm/runtime/synchronizer.cpp

    3413      OSThreadContendState osts(Self->osthread());
    3414      ThreadBlockInVM tbivm(jt);
    3415
    3416      Self->set_current_pending_monitor(this);
    3417
    3418      // TODO-FIXME: change the following for(;;) loop to straight-line code.
    3419      for (;;) {
    3420        jt->set_suspend_equivalent();
    3421        // cleared by handle_special_suspend_equivalent_condition()
    3422        // or java_suspend_self()
    3423
    3424        EnterI (THREAD) ;
    3425
    3426        if (!ExitSuspendEquivalent(jt)) break ;
    3427
    3428        //
    3429        // We have acquired the contended monitor, but while we were
    3430        // waiting another thread suspended us. We don't want to enter
    3431        // the monitor while suspended because that would surprise the
    3432        // thread that suspended us.
    

    克里斯

    【讨论】:

      【解决方案2】:

      线程转储是如何进行的?如果线程没有暂停,锁的所有权可能会在转储一个线程和下一个线程之间发生变化。

      【讨论】:

      • 通过向进程发送 QUIT 信号。我不知道 Sun VM 在线程转储期间的行为。但我假设该过程已停止。否则你会得到一个不一致的线程转储。
      • 我知道对于 IBM JVM 这不一定正确,但对于 SUN 不确定,但一定要牢记。
      • 本站声称所有线程都已暂停:expertodev.wordpress.com/2009/05/30/…
      【解决方案3】:

      我认为相关信息是:“等待监视器进入”,这两个线程都是一样的。由于两个线程(在线程转储中)都被标记为守护线程,我想肯定还有一个主线程同时运行。主线程是否可能是阻塞其他两个线程的当前监视器所有者?

      【讨论】:

      • 不,主线程什么都不做。即使主线程持有锁,规范也不区分主线程和守护线程。只允许一个线程拥有锁。
      • 我同意,只允许一个线程获得锁并进入临界区(根据规范)。正如线程转储中所述,两个守护线程都在等待锁定。您确定当前没有其他线程持有该锁吗?
      • 两个线程 http-8080-136 和 http-8080-111 持有锁。线程转储中还有另外 44 个线程“等待”锁定。
      • “持有锁”到底是什么意思?线程 111 和 136 被阻塞,因为它们试图获取当前由另一个线程持有的锁。我错了,还是这 44 个线程都被阻塞了,试图获取锁?或者 44 个线程中的任何一个都完成了它的执行?
      • http-8080-136 和 http-8080-111 的线程转储显示:已锁定 。所以他们已经获得了锁。剩余 44 个线程的线程转储显示:等待锁定 。
      【解决方案4】:

      他们没有获得锁,否则你会在堆栈跟踪中看到 xsMethodA 或 xsMethodB。

      【讨论】:

      • 那么为什么线程 http-8080-111 和 http-8080-146 有区别呢?
      • 并且:ThreadDumpAnalyzer 将两个线程(http-8080-136、http-8080-111)都列为“锁定者”。
      • 我的意思是标题“同步不阻塞”是错误的。可能你的意思是“没有人持有锁时同步块块”?
      • 这不是真的。两个线程正在持有锁。 44 个线程正在等待锁。
      • 您认为他们持有锁,但他们没有。否则 xsMethodA 和 xsMethodB 几乎肯定会在堆栈跟踪中。
      猜你喜欢
      • 2018-04-06
      • 2023-03-14
      • 2013-07-20
      • 2013-05-07
      • 1970-01-01
      • 2023-03-07
      • 2017-08-13
      • 1970-01-01
      相关资源
      最近更新 更多