多个Java线程似乎锁定了同一个监视器?

11

在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)

重点在于,这两个线程都锁定了监视器 <0x0000000682f99d98>(无论它们现在等待两个不同的其他监视器)。

查看线程转储分析器时,选择该监视器,底部确实显示“锁定监视器的线程: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()。如果正确,请接受。 - Gray
我们正在使用Jackrabbit版本1.6.5。我的一个朋友还看到在版本2.3.6中,同一行号上的wait()似乎非常匹配,但不幸的是那是错误的源代码... - jfrantzius
4个回答

3
我认为你的线程转储并没有说你的两个线程正在“等待两个不同的其他监视器”。我认为它是在说它们都在等待同一个监视器,但在两个不同的代码点上。这可能是堆栈位置、对象实例位置或其他什么东西。这是一份关于分析堆栈转储的绝佳文档。
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() 实例中从相同的代码行。这是我的愚蠢的小测试程序。请注意,监视器条目编号不同,尽管它是相同的锁和相同的代码行号。
"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上,格式不同,但是对于相同的行号,“monitor entry”后面的数字也不一样。
"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)

这份 Oracle 文档 将该值描述为以下内容:

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


有趣的事情!我一直以为显示器的数字是锁定对象的地址,但显然不是。也许挖掘JVM源代码找到这些跟踪信息生成的地方并查看这些数字的来源并不是那么难。尽管如此,我必须承认我现在不想立即去做。 - Tom Anderson
感谢那个测试!转储中的两个线程正在等待它们之前锁定的相同对象,因此它们实际上都已经释放了锁。这是一个例子,说明如何在一个线程转储中为同一个监视器的两个不同线程显示两个“锁定”事件,但仍与我的线程转储不同。那里的线程没有等待(wait())并且没有释放(on the same monitor)它们似乎都获得了的锁,并且这违反了JVM规范。你关于我的“监视器进入”地址的误解是正确的,只有Sun似乎确切知道它的含义... - jfrantzius
所以 @jfrantzius,锁看起来是在第253行 readLock = acquireReadLock(id);。我已经更新了我的答案并提供了该锁的详细信息。它是通过接口隐藏的锁类型。我对共享锁能够像那样出现在堆栈跟踪中感到印象深刻。 - Gray
@Gray,我们也遇到了类似的问题。我试着在这里发布细节作为评论,但是太长了。所以我已经发布了一个新答案。请让我们知道您的想法。 - Arnab Biswas
已将其作为一个新问题添加到 http://stackoverflow.com/questions/17464152/multiple-threads-acquiring-the-same-monitor/17464272?noredirect=1#17464272 - Arnab Biswas
显示剩余5条评论

2
您可能在分析竞争激烈的锁时遇到了 Java 虚拟机堆栈跟踪例程中的一个美学 bug,这可能与 this bug 相同,也可能不同。
事实是,您的两个线程都没有成功获取 SharedItemStateManager 上的锁,因为它们报告了 waiting for monitor entry。该 bug 是这两种情况的堆栈跟踪上面应该报告 waiting to lock 而不是 locked
当分析此类奇怪的堆栈跟踪时的解决方法是,始终检查声称已经locked对象的线程是否还在等待获取相同对象的锁。
很遗憾,这个分析需要将堆栈跟踪中的行号与源代码进行交叉引用,因为在“waiting for monitor entry”标题中的数字与堆栈跟踪中的“locked”行之间没有关系。根据这份Oracle文档,在行TP-Processor184" daemon prio=10 tid=0x00007f2a7c056800 nid=0x47e7 waiting for monitor entry [0x00007f2a21278000]中的数字0x00007f2a21278000是对线程有效堆栈区域的估计。所以它看起来像是监视器ID,但实际上不是 - 而且你可以看到你提供的两个线程在堆栈中的地址是不同的。

堆栈跟踪例程中的一个视觉缺陷听起来合理,我觉得比监视器不是独占的要好得多 :)关于你的解决方法(用于判断发生了什么),这两个线程没有锁定相同的对象(<0x0000000682f99d98>),它们正在等待获取锁([0x00007f2a21278000])? - jfrantzius
很遗憾,在Gray的回答及相关评论中,“monitor entry”之后的十六进制字符串指代仍不清楚。在分析堆栈跟踪时,我总是忽略它。 - rxg
@jfrantzius Oracle已发布文档,澄清线程转储标题中不同字段的含义,我已更新我的答案。 - rxg

2

当一个线程锁定了一个对象,但等待另一个线程时,另一个线程可以锁定相同的对象。你应该能够看到许多线程“持有”相同的锁,全部在等待。

据我所知,唯一的其他情况是当多个线程已经锁定并等待,并准备重新获取锁,例如在notifyAll()上。他们不再等待,但必须再次获得锁才能继续(每次只能有一个线程这样做)。


只是为了澄清@Peter,您的意思是:“当一个线程锁定一个对象但wait()在同一对象上等待另一个线程…” - Gray
@Peter:没错,这些线程并没有在同一个对象上等待,而是在两个不同的对象上(如果这些十六进制数字代表对象的内存地址的话...) - jfrantzius
好的,他们仍然没有在同一个对象上进行wait(),但他们正在等待某些东西(可能是GC)... 无论如何,他们没有通过调用wait()释放锁,但似乎他们都已经进入了“synchronized (this)”来使用相同的对象“this”。 - jfrantzius

0
"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个其他线程正在等待锁。多个线程持有相同的锁?说这只存在于线程转储中,因为线程转储不是原子操作。

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