我有一个应用程序,它有一个写入线程和8个读取器线程访问共享资源,该资源位于ReentrantReadWriteLock后面。它冻结了大约一个小时,没有产生日志输出,也没有响应请求。这是在Java 8上。
在杀死它之前,有人使用线程转储,这些线程转储如下所示:
写作线索:
"writer-0" #83 prio=5 os_prio=0 tid=0x00007f899c166800 nid=0x2b1f waiting on condition [0x00007f898d3ba000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000002b8dd4ea8> (a java.util.concurrent.locks.ReentrantReadWriteLock$FairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantReadWriteLock$WriteLock.lock(ReentrantReadWriteLock.java:943)读者:
"reader-1" #249 daemon prio=5 os_prio=0 tid=0x00007f895000c000 nid=0x33d6 waiting on condition [0x00007f898edcf000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x00000002b8dd4ea8> (a java.util.concurrent.locks.ReentrantReadWriteLock$FairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireShared(AbstractQueuedSynchronizer.java:967)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireShared(AbstractQueuedSynchronizer.java:1283)
at java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock.lock(ReentrantReadWriteLock.java:727)这看起来像是一个僵局,但是有几件事让我怀疑:
parking to wait for <0x00000002a7daa878>,这与第一个转储中的0x00000002b8dd4ea8不同。这是僵局吗?我看到线程的状态有一些变化,但它只能是锁实现的内部。还有什么原因会导致这种行为?
发布于 2021-07-22 13:27:18
原来是个僵局。持有锁的线程未被报告为在线程转储中持有任何锁,因此很难诊断。
了解这一点的唯一方法是检查应用程序的堆转储。对于那些对方法感兴趣的人来说,下面是一步一步的过程:
ReentrantReadWriteLock对象。特别是,该锁的sync字段保持其状态--在本例中是ReentrantReadWriteLock$FairSync。state属性是65536。这表示了锁的共享和独占持有的数量。共享持有计数存储在状态的前16位中,并作为state >>> 16检索。独占保持计数在最后16位中,被检索为state & ((1 << 16) - 1)。从这一点我们可以看到,在锁上有一个共享的持有和零的独占持有。head字段中看到等待锁的线程。它是一个队列,thread包含等待线程,next包含队列中的下一个节点。通过它,我找到了writer-0和8个reader-n线程中的7个,确认了我们从线程转储中知道的内容。firstReader字段包含从代码firstReader is the first thread to have acquired the read lock. firstReaderHoldCount is firstReader's hold count.More precisely, firstReader is the unique thread that last changed the shared count from 0 to 1, and has not released theread lock since then; null if there is no such thread.中的注释中获取读取日志的线程。在这种情况下,持有锁的线程是reader线程之一。它是在完全不同的东西上被阻塞的,这需要其他reader线程中的一个进行。最终,它是由一个错误引起的,在这个错误中,读取器线程不能正确地释放锁,并且永远保持它。我是通过分析代码,并在获取和释放锁时添加跟踪和日志记录发现的。
发布于 2021-07-08 11:04:19
这是僵局吗?
我不认为这是僵局的证据。至少,不是这个词的经典意义上的。
堆栈转储显示两个线程在同一ReentrantReadWriteLock上等待。一个线程正在等待获取读锁。另一个正在等待获取写锁。
现在,如果当前没有线程持有任何锁,那么其中一个线程将能够继续执行。
如果其他线程当前持有写锁,则这足以阻塞这两个线程。但那不是僵局。只有当第三个线程本身在等待另一个锁时,这才是死锁.在阻塞中有一个循环。
那么这两个线程相互阻塞的可能性呢?我不认为那是可能的。javadocs中的重入规则允许具有写锁的线程在不阻塞的情况下获取读锁。同样,它可以获得它已经持有的写锁。
另一项证据是,在稍后的线程转储中,情况发生了变化。如果出现真正的僵局,就不会有任何变化。
如果这不是(仅仅)这两个线程之间的死锁,它还能是什么呢?
一种可能是第三个线程持有写锁(很长一段时间),这就把事情粘住了。关于这个readwrite锁的争论太多了。
如果(假定)第三个线程使用的是tryLock,则有可能有一个活锁.这可以解释“改变”的证据。但另一方面,那根线也应该停在.你说你看不见。
另一种可能是你有太多的活动线程。而操作系统正在努力将它们安排在核心上。
但这都是猜测。
https://stackoverflow.com/questions/68298055
复制相似问题