【问题标题】:Multiple Java threads seemingly locking same monitor?多个 Java 线程看似锁定同一个监视器?
【发布时间】:2012-03-21 04:09:40
【问题描述】:

在 Java 线程转储中,我发现了以下内容:

"TP-Processor184" daemon prio=10 tid=0x00007f2a7c056800 nid=0x47e7 waiting for monitor entry [0x00007f2a21278000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at org.apache.jackrabbit.core.state.SharedItemStateManager.getNonVirtualItemState(SharedItemStateManager.java:1725)
    - locked <0x0000000682f99d98> (a org.apache.jackrabbit.core.state.SharedItemStateManager)
    at org.apache.jackrabbit.core.state.SharedItemStateManager.getItemState(SharedItemStateManager.java:257)

"TP-Processor137" daemon prio=10 tid=0x00007f2a7c00f800 nid=0x4131 waiting for monitor entry [0x00007f2a1ace7000]
   java.lang.Thread.State: BLOCKED (on object monitor)
    at org.apache.jackrabbit.core.state.SharedItemStateManager.getNonVirtualItemState(SharedItemStateManager.java:1725)
    - locked <0x0000000682f99d98> (a org.apache.jackrabbit.core.state.SharedItemStateManager)
    at org.apache.jackrabbit.core.state.SharedItemStateManager.getItemState(SharedItemStateManager.java:257)

这里的重点是两个线程都锁定了监视器(不管它们现在等待两个不同的其他监视器)。

查看线程转储分析器时,选择了该监视器,它实际上在底部显示“线程锁定监视器:2”和“2 线程锁定”。截图请看https://lh4.googleusercontent.com/-fCmlnohVqE0/T1D5lcPerZI/AAAAAAAAD2c/vAHcDiGOoMo/s971/locked_by_two_threads_3.png,这里不允许贴图。

这是否意味着线程转储在监视锁定信息方面不是原子的?我无法想象这真的是 JVM (1.6.0_26-b03) 的锁定错误。

Can several threads hold a lock on the same monitor in Java? 中已经提出了类似的问题,但我的回答没有看到多个线程锁定同一个监视器的真正意义,即使它们可能正在等待其他监视器。

2014 年 5 月 13 日更新:

较新的问题Multiple threads hold the same lock? 具有重现该行为的代码,@rsxg 已按照他在此处的回答的内容提交了相应的错误报告https://bugs.openjdk.java.net/browse/JDK-8036823

【问题讨论】:

  • 对我的回答有什么反馈吗?我对其进行了编辑以指出更高版本的代码在第 1725 行有一个wait()。如果这是正确的,请接受。
  • 我们使用的是 Jackrabbit 版本 1.6.5。我的一个朋友也在 2.3.6 版本的同一行号上看到了 wait(),因为它看起来非常匹配,但不幸的是,源代码是错误的......

标签: java multithreading concurrency locking


【解决方案1】:

我不认为您的线程转储是说您的两个线程正在“等待两个不同的其他监视器”。我认为这是说他们都在同一个监视器上等待,但在两个不同的代码点。那可能是堆栈位置或对象实例位置或其他东西。这是一个关于analyzing the stack dumps 的很棒的文档。

Java 中可以多个线程在同一个监视器上持有锁吗?

没有。您的堆栈转储显示两个线程锁定在同一监视器上的同一代码位置但在不同的堆栈帧中 - 或者看起来与操作系统相关的任何值。

编辑:

我不确定为什么线程转储似乎说两个线程都锁定了一条线,因为这似乎只有在它们位于 wait() 方法中时才被允许。我注意到您正在链接到版本 1.6.5。这真的是您使用的版本吗?在 2.3.6 版本(可能是最新版本)中,1725 line 实际上是 wait

1722        synchronized (this) {
1723            while (currentlyLoading.contains(id)) {
1724                try {
1725                    wait();
1726                } catch (InterruptedException e) {

即使它是独占的synchronized 锁,您也可以看到这种堆栈跟踪。例如,Linux 下的以下堆栈转储针对来自同一代码行但在 Runnable.run() 方法的两个不同实例中锁定在同一对象上的两个线程。这是我的stupid little test program。请注意,监视器条目号是不同的,即使它是相同的锁和相同的代码行号。

"Thread-1" prio=10 tid=0x00002aab34055c00 nid=0x4874
  waiting for monitor entry [0x0000000041017000..0x0000000041017d90]
java.lang.Thread.State: BLOCKED (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00002aab072a1318> (a java.lang.Object)
    at com.mprew.be.service.auto.freecause.Foo$OurRunnable.run(Foo.java:38)
    - locked <0x00002aab072a1318> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:619)

"Thread-0" prio=10 tid=0x00002aab34054c00 nid=0x4873
  waiting for monitor entry [0x0000000040f16000..0x0000000040f16d10]
java.lang.Thread.State: BLOCKED (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00002aab072a1318> (a java.lang.Object)
    at com.mprew.be.service.auto.freecause.Foo$OurRunnable.run(Foo.java:38)
    - locked <0x00002aab072a1318> (a java.lang.Object)
    at java.lang.Thread.run(Thread.java:619)

在我的 Mac 上,格式不同,但同样的行号,“监视器条目”后面的数字也不相同。

"Thread-2" prio=5 tid=7f8b9c00d000 nid=0x109622000
  waiting for monitor entry [109621000]
java.lang.Thread.State: BLOCKED (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <7f3192fb0> (a java.lang.Object)
    at com.mprew.be.service.auto.freecause.Foo$OurRunnable.run(Foo.java:38)
    - locked <7f3192fb0> (a java.lang.Object)

"Thread-1" prio=5 tid=7f8b9f80d800 nid=0x10951f000
  waiting for monitor entry [10951e000]
java.lang.Thread.State: BLOCKED (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <7f3192fb0> (a java.lang.Object)
    at com.mprew.be.service.auto.freecause.Foo$OurRunnable.run(Foo.java:38)
    - locked <7f3192fb0> (a java.lang.Object)

This Oracle document 将该值描述如下:

地址范围,给出线程有效堆栈区域的估计值

【讨论】:

  • 有趣的东西!我一直认为监视器的数字是锁定对象的地址,但显然不是。挖掘 JVM 源代码以找到构建这些跟踪的位置并查看这些数字的来源可能并不难。不过,我必须承认,我现在不想这样做。
  • 感谢您的测试!但是,转储中的两个线程正在等待()它们之前锁定的同一个对象,因此它们实际上都释放了锁。这是如何在同一监视器的两个不同线程的线程转储中显示两个“锁定”事件的示例,但仍然与我的线程转储不同。那里的线程没有 wait() 并且没有释放它们似乎都获得的锁(在同一个监视器上),这违反了 JVM 规范。您对我对“监视器条目”地址的误解是正确的,似乎只有 Sun 确切地知道它的含义......
  • 我不同意@jfrantzius。我认为您的堆栈跟踪是相同的。线程已锁定相同的锁 (0x0000000682f99d98) 并等待它。因为他们现在是BLOCKED,我认为这意味着他们已经停止等待——无论是收到通知还是等待超时。如果你运行我的小测试程序并调整超时,你可以看到线程从等待变为阻塞。
  • @Gray sourcecode 中没有 wait() ,就像你的一样。只有一个同步块。
  • 忽略 JVM 规范的矛盾,下一个问题是:那么线程到底在等待什么? Oracle Forum 表明这是垃圾收集将线程置于 BLOCKED 状态,this Stack Overflow question 也是如此。但这只是目前的猜测。
【解决方案2】:

在分析严重竞争锁时,您可能在 JVM 的堆栈跟踪例程中遇到了一个装饰性错误 - 它可能与 this bug 相同,也可能不同。

事实是,您的两个线程实际上都没有设法获得SharedItemStateManager 上的锁,从它们报告waiting for monitor entry 的事实中可以看出。错误在于,在这两种情况下,在堆栈跟踪的更深处,他们应该报告 waiting to lock 而不是 locked

在分析类似这样的奇怪堆栈跟踪时,解决方法是始终检查声称拥有locked 对象的线程是否也在等待获取同一对象的锁定。

(不幸的是,此分析需要将堆栈跟踪中的行号与源代码交叉引用,因为waiting for monitor entry 标头中的数字与堆栈跟踪中的locked 行之间没有关系。根据this Oracle documentTP-Processor184" daemon prio=10 tid=0x00007f2a7c056800 nid=0x47e7 waiting for monitor entry [0x00007f2a21278000] 行中的数字 0x00007f2a21278000线程的有效堆栈区域的估计。所以它看起来像一个监视器 ID,但它不是 - 而你可以看到你给的两个线程在栈中的不同地址)。

【讨论】:

  • 堆栈跟踪例程中的一个装饰性错误听起来很明智,我觉得它比监视器不是排他性的要好得多:) 关于你的解决方法(用于决定发生了什么),这两个线程没有'不锁定他们正在等待获取锁定 ([0x00007f2a21278000]) 的同一个对象 (&lt;0x0000000682f99d98&gt;)?
  • 不幸的是,不清楚“监视器条目”之后的十六进制字符串指的是什么,如 Gray 和相关 cmets 的回答。在分析堆栈跟踪时,我总是忽略它。
  • @jfrantzius Oracle 已发布文档,阐明线程转储标头中不同字段的含义,我更新了答案。
【解决方案3】:

当一个线程锁定一个对象但 wait()s 另一个线程可以锁定同一个对象。您应该能够看到许多线程“持有”同一个锁,都在等待。

AFAIK,唯一的其他情况是多个线程已锁定并等待并准备重新获取锁,例如在 notifyAll() 上。他们不再等待,但在再次获得锁之前无法继续。 (一次只有一个线程可以这样做)

【讨论】:

  • 只是为了澄清@Peter,您的意思是:“当一个线程锁定一个对象但 wait()s on_that_same_object 另一个线程......”
  • @Peter:确切地说,线程不是在同一个对象上等待,而是在两个不同的对象上(如果那些十六进制数字表示对象的内存地址,那就是。 ..)
  • 好的,他们仍然没有在同一个对象上等待(),但他们正在等待 something (可能是 GC)......无论如何,他们没有释放通过调用wait()锁定,但他们似乎都为同一个对象“this”输入了“同步(this)”。
【解决方案4】:
"http-0.0.0.0-8080-96" daemon prio=10 tid=0x00002abc000a8800 nid=0x3bc4 waiting for monitor entry [0x0000000050823000]
    java.lang.Thread.State: BLOCKED (on object monitor)
    at org.apache.lucene.search.FieldCacheImpl$Cache.get(FieldCacheImpl.java:195)
    - locked <0x00002aadae12c048> (a java.util.WeakHashMap)

"http-0.0.0.0-8080-289" daemon prio=10 tid=0x00002abc00376800 nid=0x2688 waiting for monitor entry [0x000000005c8e3000]
    java.lang.Thread.State: BLOCKED (on object monitor)
    at org.apache.lucene.search.FieldCacheImpl$Cache.get(FieldCacheImpl.java:195)
    - locked <0x00002aadae12c048> (a java.util.WeakHashMap

"http-0.0.0.0-8080-295" daemon prio=10 tid=0x00002abc00382800 nid=0x268e runnable [0x000000005cee9000]
     java.lang.Thread.State: RUNNABLE
     at org.apache.lucene.search.FieldCacheImpl$Cache.get(FieldCacheImpl.java:195)
     - locked <0x00002aadae12c048> (a java.util.WeakHashMap)

在我们的线程转储中,我们有多个线程锁定同一个监视器,但只有一个线程可运行。可能是因为锁竞争,我们有 284 个其他线程在等待锁。 Multiple threads hold the same lock? 说这只存在于线程转储中,因为线程转储不是原子操作。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多