涉及Swing和AWT-EventQueue的无响应线程

我有一个没有响应的应用程序,似乎陷入僵局或类似僵局。 请参阅下面的两个主题。 请注意, My-Thread@101c线程阻止AWT-EventQueue-0@301 。 但是, My-Thread刚刚调用了java.awt.EventQueue.invokeAndWait() 。 所以AWT-EventQueue-0阻止My-Thread (我相信)。

 My-Thread@101c, priority=5, in group 'main', status: 'WAIT' blocks AWT-EventQueue-0@301 at java.lang.Object.wait(Object.java:-1) at java.lang.Object.wait(Object.java:485) at java.awt.EventQueue.invokeAndWait(Unknown Source:-1) at javax.swing.SwingUtilities.invokeAndWait(Unknown Source:-1) at com.acme.ui.ViewBuilder.renderOnEDT(ViewBuilder.java:157) . . . at com.acme.util.Job.run(Job.java:425) at java.lang.Thread.run(Unknown Source:-1) AWT-EventQueue-0@301, priority=6, in group 'main', status: 'MONITOR' waiting for My-Thread@101c at com.acme.persistence.TransactionalSystemImpl.executeImpl(TransactionalSystemImpl.java:134) . . . at com.acme.ui.components.MyTextAreaComponent$MyDocumentListener.insertUpdate(MyTextAreaComponent.java:916) at javax.swing.text.AbstractDocument.fireInsertUpdate(Unknown Source:-1) at javax.swing.text.AbstractDocument.handleInsertString(Unknown Source:-1) at javax.swing.text.AbstractDocument$DefaultFilterBypass.replace(Unknown Source:-1) at javax.swing.text.DocumentFilter.replace(Unknown Source:-1) at com.acme.ui.components.FilteredDocument$InputDocumentFilter.replace(FilteredDocument.java:204) at javax.swing.text.AbstractDocument.replace(Unknown Source:-1) at javax.swing.text.JTextComponent.replaceSelection(Unknown Source:-1) at javax.swing.text.DefaultEditorKit$DefaultKeyTypedAction.actionPerformed(Unknown Source:-1) at javax.swing.SwingUtilities.notifyAction(Unknown Source:-1) at javax.swing.JComponent.processKeyBinding(Unknown Source:-1) at javax.swing.JComponent.processKeyBindings(Unknown Source:-1) at javax.swing.JComponent.processKeyEvent(Unknown Source:-1) at java.awt.Component.processEvent(Unknown Source:-1) at java.awt.Container.processEvent(Unknown Source:-1) at java.awt.Component.dispatchEventImpl(Unknown Source:-1) at java.awt.Container.dispatchEventImpl(Unknown Source:-1) at java.awt.Component.dispatchEvent(Unknown Source:-1) at java.awt.KeyboardFocusManager.redispatchEvent(Unknown Source:-1) at java.awt.DefaultKeyboardFocusManager.dispatchKeyEvent(Unknown Source:-1) at java.awt.DefaultKeyboardFocusManager.preDispatchKeyEvent(Unknown Source:-1) at java.awt.DefaultKeyboardFocusManager.typeAheadAssertions(Unknown Source:-1) at java.awt.DefaultKeyboardFocusManager.dispatchEvent(Unknown Source:-1) at java.awt.Component.dispatchEventImpl(Unknown Source:-1) at java.awt.Container.dispatchEventImpl(Unknown Source:-1) at java.awt.Window.dispatchEventImpl(Unknown Source:-1) at java.awt.Component.dispatchEvent(Unknown Source:-1) at java.awt.EventQueue.dispatchEvent(Unknown Source:-1) at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source:-1) at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source:-1) at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source:-1) at java.awt.EventDispatchThread.pumpEvents(Unknown Source:-1) at java.awt.EventDispatchThread.pumpEvents(Unknown Source:-1) at java.awt.EventDispatchThread.run(Unknown Source:-1) 

这是TransactionalSystemImpl.executeImpl方法:

 private synchronized Object executeImpl(Transaction xact, boolean commit) { final Object result; try { if (commit) { // this is line 134 clock.latch(); synchronized(pendingEntries) { if (xactLatchCount > 0) { pendingEntries.add(xact); } else { xactLog.write(new TransactionEntry(xact, clock.time())); } } } final TransactionExecutor executor = transactionExecutorFactory.create( xact.getClass().getSimpleName() ); if (executor == null) { throw new IllegalStateException("Failed to create transaction executor for transaction: " + xact.getClass().getName()); } result = executor.execute(xact); } finally { if (commit) clock.unlatch(); } return result; } 

有谁知道这里发生了什么或如何解决它?

我认识的Swing开发人员似乎很熟悉invokeAndWait是有问题的,但也许这并不像我想象的那么出名。 我似乎记得在文档中看到了关于正确使用invokeAndWait困难的严厉警告,但我很难找到任何东西。 我在当前的官方文档中找不到任何内容。 我唯一能找到的就是来自2005年Swing Tutorial的旧版本的这一行:(网络档案)

如果使用invokeAndWait ,请确保调用invokeAndWait的线程不会保留调用发生时其他线程可能需要的任何锁。

不幸的是,这一行似乎已经从当前的Swing教程中消失了。 即便如此,这也是轻描淡写; 我更喜欢它说“如果你使用invokeAndWait ,调用invokeAndWait的线程不能保持调用发生时其他线程可能需要的任何锁定。” 通常,很难知道在任何给定时间内其他线程可能需要锁定什么,最安全的策略可能是确保调用invokeAndWait的线程根本不保留任何锁定

(这很难做到,这就是为什么我上面说invokeAndWait是有问题的。我也知道JavaFX的设计者 – 本质上是一个Swing替换 – 在javafx.application.Platform类中定义了一个名为runLater的方法,它是在function上等同于invokeLater 。但是他们故意省略了invokeAndWait的等效方法,因为它很难正确使用。)

原因很简单,可以从第一原则中得出。 考虑一个与OP描述的系统类似的系统,它有两个线程:MyThread和Event Dispatch Thread(EDT)。 MyThread锁定对象L,然后调用invokeAndWait 。 这会发布事件E1并等待它由EDT处理。 假设E1的处理程序需要锁定L.当EDT处理事件E1时,它会尝试锁定L.这个锁已经由MyThread保持,在EDT处理E1之前不会放弃它,但是该处理被阻止通过MyThread。 因此我们陷入僵局。

以下是此方案的变体。 假设我们确保处理E1不需要锁定L.这是否安全? 不会。如果在MyThread调用invokeAndWait之前,事件E0被发布到事件队列,并且E0的处理程序需要锁定L,问题仍然可能发生。如前所述,MyThread保持对L的锁定,因此阻止了E0的处理。 E1在事件队列中落后于E0,因此E1的处理也被阻止。 由于MyThread正在等待E1被处理,并且它被E0阻止,而E0又被阻止等待MyThread放弃对L的锁定,我们再次陷入僵局。

这听起来与OP的应用程序中发生的情况非常相似。 根据OP 对此答案的评论,

是的,renderOnEDT在调用堆栈中以某种方式同步,com.acme.persistence.TransactionalSystemImpl.executeImpl方法是同步的。 并且renderOnEDT正在等待进入相同的方法。 所以,这就是它看起来像死锁的根源。 现在我必须弄清楚如何解决它。

我们没有完整的图片,但这可能已经足够了。 从MyThread调用renderOnEDT ,它在invokeAndWait被阻塞时对某些东西进行了锁定。 它正在等待EDT处理事件,但我们可以看到EDT在MyThread所持有的东西上被阻止。 我们无法准确地确定这是什么对象,但它有点无关紧要 – 在MyThread持有的锁上EDT被明显阻止,而MyThread显然正在等待EDT处理事件:因此,死锁。

另请注意,我们可以相当确定EDT当前没有处理invokeAndWait发布的事件(类似于上面我的场景中的E1)。 如果是的话,每次都会发生僵局。 它似乎有时只会发生,并且根据OP 对此答案的评论,当用户快速键入时。 所以我敢打赌,EDT目前正在处理的事件是在MyThread锁定之后碰巧被发布到事件队列的击键,但是在MyThread调用invokeAndWait将E1发布到事件队列之前,因此它类似于E0在上面的场景中。

到目前为止,这可能主要是对问题的回顾,从其他答案和OP对这些答案的评论拼凑而成。 在我们继续讨论解决方案之前,以下是我对OP应用程序的一些假设:

  • 它是multithreading的,因此必须同步各种对象才能正常工作。 这包括来自Swing事件处理程序的调用,可能会根据用户交互更新某些模型,并且此模型也由工作线程(如MyThread)处理。 因此,他们必须正确锁定这些对象。 删除同步肯定会避免死锁,但是由于数据结构被非同步的并发访问破坏,其他错误将会蔓延。

  • 该应用程序不一定在EDT上执行长时间运行的操作。 这是GUI应用程序的典型问题,但这似乎不会发生在这里。 我假设应用程序在大多数情况下工作正常,其中在EDT上处理的事件抓取锁,更新某些内容,然后释放锁。 当它无法获得锁定时会发生问题,因为锁定的持有者在EDT上死锁。

  • invokeAndWait更改为invokeLater不是一个选项。 OP表示,这样做会导致其他问题。 这并不奇怪,因为这种改变会导致执行以不同的顺序发生,因此它会产生不同的结果。 我认为他们是不可接受的。

如果我们无法删除锁,我们无法更改为invokeLater ,我们将安全地调用invokeAndWait 。 并且“安全”意味着在调用之前放弃锁定。 考虑到OP的应用程序的组织,这可能是任意难做的,但我认为这是唯一的方法。

让我们来看看MyThread正在做什么。 这是非常简化的,因为堆栈上可能有一堆干预方法调用,但从根本上说它是这样的:

 synchronized (someObject) { // code block 1 SwingUtilities.invokeAndWait(handler); // code block 2 } 

当某个事件在处理程序前面的队列中潜入时,会出现问题,并且该事件的处理需要锁定someObject 。 我们怎样才能避免这个问题? 您无法在synchronized块中放弃Java的内置监视器锁定,因此您必须关闭该块,拨打电话并再次打开它:

 synchronized (someObject) { // code block 1 } SwingUtilities.invokeAndWait(handler); synchronized (someObject) { // code block 2 } 

如果someObject上的锁被调用到invokeAndWait的调用堆栈相当远,那么这可能是任意的困难,但我认为进行这种重构是不可避免的。

还有其他陷阱。 如果代码块2依赖于代码块1加载的某些状态,那么在代码块2再次进行锁定时,该状态可能已经过时。 这意味着代码块2必须从同步对象重新加载任何状态。 它不得基于代码块1的结果做出任何假设,因为这些结果可能已过时。

这是另一个问题。 假设由invokeAndWait运行的处理程序需要从共享对象加载某些状态,例如,

 synchronized (someObject) { // code block 1 SwingUtilities.invokeAndWait(handler(state1, state2)); // code block 2 } 

您不能只将invokeAndWait调用移出synchronized块,因为这需要获得state1和state2的非同步访问。 您需要做的是在锁定内将此状态加载到局部变量中,然后在释放锁定后使用这些局部变量进行调用。 就像是:

 int localState1; String localState2; synchronized (someObject) { // code block 1 localState1 = state1; localState2 = state2; } SwingUtilities.invokeAndWait(handler(localState1, localState2)); synchronized (someObject) { // code block 2 } 

释放锁之后进行调用的技术称为开放调用技术。 参见Doug Lea, Java中的并发编程 (第2版),第2.4.1.3节。 在Goetz等人中也对这种技术进行了很好的讨论。 例如, Java Concurrency In Practice ,第10.1.4节。 事实上,10.1节中的所有内容都相当彻底地涵盖了死锁; 我高度推荐它。

总而言之,我相信使用上面描述的技术或引用的书籍将正确和安全地解决这个死锁问题。 但是,我确信这需要进行大量的仔细分析和重组。 但是,我没有看到另一种选择。

(最后,我应该说,虽然我是Oracle的员工,但这绝不是甲骨文的正式声明。)


UPDATE

我想到了一些可能有助于解决问题的潜在重构。 让我们重新考虑代码的原始模式:

 synchronized (someObject) { // code block 1 SwingUtilities.invokeAndWait(handler); // code block 2 } 

这按顺序执行代码块1,处理程序和代码块2。 如果我们要将invokeAndWait调用更改为invokeLater ,则处理程序将在代码块2之后执行。可以很容易地看到这将是应用程序的问题。 相反,我们如何将代码块2移动 invokeAndWait以便它以正确的顺序执行,但仍然在事件线程上?

 synchronized (someObject) { // code block 1 } SwingUtilities.invokeAndWait(Runnable { synchronized (someObject) { handler(); // code block 2 } }); 

这是另一种方法。 我invokeAndWait知道传递给invokeAndWait的处理程序是做什么的。 但是它可能需要invokeAndWait一个原因是它从GUI中读取一些信息,然后使用它来更新共享状态。 这必须在EDT上,因为它与GUI对象交互,并且不能使用invokeLater因为它会以错误的顺序发生。 这建议在进行其他处理之前调用invokeAndWait以便将信息从GUI读取到临时区域,然后使用此临时区域执行继续处理:

 TempState tempState; SwingUtilities.invokeAndWait(Runnable() { synchronized (someObject) { handler(); tempState.update(); } ); synchronized (someObject) { // code block 1 // instead of invokeAndWait, use tempState from above // code block 2 } 

寻找可靠和/或官方来源的答案。

Event Dispatch Thread and EventQueue

Swing事件处理代码在称为事件调度线程(EDT)的特殊线程上运行。 大多数调用Swing方法的代码也在这个线程上运行。 这是必要的,因为大多数Swing对象方法都不是线程安全的 。 所有与GUI相关的任务,都应该对GUI进行任何更新,而绘制过程必须在EDT上进行, 这涉及将请求包装在事件中并将其处理到EventQueue 。 然后,事件将从一个接一个的队列中按顺序分派,即FIRST IN FIRST OUT。 也就是说,如果Event AEvent B之前排队到EventQueue ,那么事件B将不会在事件A之前被调度。

SwingUtilities类有两个有用的function来帮助GUI渲染任务:

  • invokeLater(Runnable) :使doRun.run()在AWT事件派发线程(EDT)上异步执行。 如上所述,这将在所有待处理的AWT事件被处理之后发生。
  • invokeAndWait(Runnable) :它具有与invokeLater相同的function,但它与invokeLater不同之处在于:
    1. invokeAndWait等待它给EDT的任务,在返回之前完成。
    2. 它阻塞(等待)当前(即,通过同步锁定,通过发送到WAIT状态来调用继续执行它的线程)。
    3. 一旦此函数发布的事件请求在EDT中调度并且此函数的调用程序线程可以继续,它将释放锁定。

源代码有证据:

  public static void invokeAndWait(Runnable runnable) throws InterruptedException, InvocationTargetException { if (EventQueue.isDispatchThread()) throw new Error("Cannot call invokeAndWait from the event dispatcher thread"); class AWTInvocationLock {} Object lock = new AWTInvocationLock(); InvocationEvent event = new InvocationEvent(Toolkit.getDefaultToolkit(), runnable, lock, true); synchronized (lock) { //<<---- locking Toolkit.getEventQueue().postEvent(event); while (!event.isDispatched()) { //<---- checking if the event is dispatched lock.wait(); //<---- if not tell the current invoking thread to wait } } Throwable eventThrowable = event.getThrowable(); if (eventThrowable != null) { throw new InvocationTargetException(eventThrowable); } } 

这解释了这个问题:

My-Thread刚刚调用了java.awt.EventQueue.invokeAndWait()。 所以AWT-EventQueue-0阻止My-Thread(我相信)。

为了解释您可能遇到的死锁情况,让我们看一个例子:

 class ExampleClass { public synchronized void renderInEDT(final Thread t) { try { SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { System.out.println("Executinf invokeWait's Runnable "); System.out.println("invokeWait invoking Thread's state: "+t.getState()); doOtherJob(); } }); } catch (InterruptedException ex) { Logger.getLogger(SwingUtilitiesTest.class.getName()).log(Level.SEVERE, null, ex); } catch (InvocationTargetException ex) { Logger.getLogger(SwingUtilitiesTest.class.getName()).log(Level.SEVERE, null, ex); } } public synchronized void renderInEDT2(final Thread t) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { System.out.println("Executing invokeLater's Runnable "); System.out.println("invokeLater's invoking Thread's state: "+t.getState()); doOtherJob(); } }); try { Thread.sleep(3000); } catch (InterruptedException ex) { Logger.getLogger(ExampleClass.class.getName()).log(Level.SEVERE, null, ex); } } public synchronized void doOtherJob() { System.out.println("Executing a job inside EDT"); } } 

如您所见,我已声明三个同步function:

  1. renderInEDT(final Thread t) :通过SwingUtilities.invokeAndWait在EDT中执行Runnable任务
  2. renderInEDT2(final Thread t) :通过SwingUtilities.invokeLater在EDT中执行Runnable任务
  3. doOtherJob() :这个函数由上面两个函数的Runnable 's run()方法调用。

传递调用线程的引用以检查SwingUtilities函数的每个调用的状态。 现在,如果我们在renderInEDT()的实例exmpleClass上调用renderInEDT() :这里Thread t在类上下文中声明:

  t = new Thread("TestThread"){ @Override public void run() { exmpleClass.renderInEDT(t); } }; t.start(); 

输出将是:

 Executing invokeWait's Runnable invokeWait invoking Thread's state: WAITING 

doOtherJob()方法永远不会在SwingUtilities.invokeAndWait发布的EDT中执行,因为会出现死锁情况。 当renderInEDT()被同步并在一个线程内执行即t ,EDT将需要等待执行doOtherJob()直到第一次调用线程完成执行renderInEDT(final Thread t)方法,如official tutorial source of synchronized method

对同一对象的两个同步方法的调用不可能进行交错。 当一个线程正在为对象执行同步方法时,所有其他线程都会调用同一对象的同步方法(暂停执行),直到第一个线程完成对象为止。

因此,EDT正在等待t线程完成(和暂停)执行,但是线程t实际上被阻止并通过如上所述的SwingUtilities.invokeAndWait方法发送到等待状态,因此它无法完成它的执行: Bth EDT和t线程正在等待彼此执行

让我们看一下上面的例子:如果我们使用SwingUtilities.invokeLater发布事件任务,因为如果我们从一个线程对renderInEDT2()实例执行renderInEDT2()函数就很明显了:

  t = new Thread("TestThread"){ @Override public void run() { exmpleClass.renderInEDT2(t); } }; t.start(); 

这次你会看到,函数调用继续正常产生以下输出:

 Executing invokeLater's Runnable invokeLater's invoking Thread's state: TIMED_WAITING Executing a job inside EDT 

这次doOtherJob()一旦renderInEDT2()的第一个调用线程完成执行就被EDT执行:强调我已经让线程进入hibernate状态(3s)来检查执行时序,因此它显示状态TIMED_WAITING

这就解释了你的第二个问题:正如你的一条评论中所说的那样,例外情况也是如此:

enderOnEDT在调用堆栈中以某种方式同步,即同步的com.acme.persistence.TransactionalSystemImpl.executeImpl方法。 并且renderOnEDT正在等待进入相同的方法。 所以,这就是它看起来像死锁的根源。 现在我必须弄清楚如何解决它。

但是

  • SwingUtilities.invokeAndWait(Runnable)特别用于我们想要阻止或等待线程并询问用户确认我们是否应该继续使用JOptionPane/JDialogue/JFileChooser等。
  • 否则,要在EventQueue发布GUI呈现任务,请使用SwingUtilities.invokeLater(Runnable)
  • 虽然,我等待EDT使用Thread.sleep(time)进行演示 ,请不要做任何这样的事情,这可能会阻止EDT提前一段时间,即使它很少,否则你的Swing会被冻结,会迫使你杀了它。
  • 我们不应该执行任何类型的计算工作或读/写操作或者与EDT内的GUI渲染任务无关的任何其他事情,也就是在invokeAndWaitinvokeLater函数的Runnable中。

此时,您自己应该能够找出并解决您的问题,因为您没有向我们提供足够的详细信息。 我认为,当你在评论中使用像enderOnEDT这样的同步函数将你的GUI渲染任务发布到事件队列时,我看不出有任何理由从它的Runnable调用另一个同步函数。 而是将渲染函数直接放在此Runnable 。 这是我解释事件队列和EDT机制的唯一目的。

参考:

  1. The Event Dispatch Thread
  2. Class EventQueue
  3. Initial Threads
  4. SwingUtilities.invokeAndWait(Runnable doRun) documentation

在没有看到代码的情况下很难分辨,但是从堆栈跟踪中,看起来你正在从事件调度线程中触发某种事务代码。 该代码是否会启动My-Thread实例? 可以阻止EDT在事务代码中等待My-Thread,但My-Thread无法完成,因为它需要EDT。

如果是这种情况,您可以使用SwingUtilities.invokeLater进行渲染,以便EDT完成事务代码,然后它将呈现更新。 或者,您无法从EDT执行交易代码。 对于与渲染无关的实际工作,您应该使用SwingWorker来避免在EDT上进行任何繁重的处理。

我怀疑你引用的134行不是真正的134行(可能是由陈旧的代码或其他一些问题引起的)。 似乎134正在等待监视器,这很可能意味着synchronized(pendingEntries) ,(或者我认为它是某种倒计时锁存器的clock.latch() ?)

从堆栈跟踪,AWT事件调度线程正在等待由MyThread持有的监视器。

请检查MyThread堆栈跟踪的代码库。 我相信它在pendingEntries同步,然后它使用invokeAndWait来请求事件调度线程做某事,然后事件调度线程正在等待pendingEntries ,这导致了死锁。


一个有点偏离主题的建议:您的事件调度线程看起来比它应该做的更多。 我不认为在事件调度线程中进行那些事务处理等是一个不错的选择。 此类操作可能很慢(在这种情况下,甚至会阻止事件调度线程),这将导致UI无响应。

将此类操作拆分为单独的线程/执行程序对我来说似乎是更好的选择。

某些线程(我假设My-Thread@101c )在TransactionalSystemImpl实例上synchronized 。 UI线程正在尝试进入executeImpl但在synchronized监视器上被阻止而不能。 还在哪里使用TransactionalSystemImpl实例(使用synchronized条目)? 可能介于两者之间

  at com.acme.ui.ViewBuilder.renderOnEDT(ViewBuilder.java:157) . . . at com.acme.util.Job.run(Job.java:425) 

如果没有其他死锁,您可以将对EventQueue.invokeLater(Runnable)的调用转换为阻塞版本,等待Runnable完成:

 if (EventQueue.isDispatchThread()) r.run(); else { final Lock lock = new ReentrantLock(); final AtomicBoolean locked = new AtomicBoolean(true); final Condition condition = lock.newCondition(); EventQueue.invokeLater(() -> { r.run(); try { lock.lock(); locked.set(false); condition.signalAll(); } finally { lock.unlock(); } }); try { lock.lock(); while (locked.get()) condition.await(); } finally { lock.unlock(); } } 

不允许来自EDT的invokeAndWait并且应该通过exception。 但是看看stacktrace它看起来像是因为你正在使用你的包装线程,它允许你调用invokeAndWait,但那不对。 将其更改为SwingUtilities.invokeLater应该可以解决此问题。

替代解决方案:如果它值得你也可以查看SwingWroker类的工作线程。 链接在这里:

http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html

只需添加一些有关死锁的更多信息:invokeAndWait的Javadoc清楚地提到“这将在处理完所有待处理事件后发生”。 这包括当前事件正在调用invokeAndWait。 invokeAndWait将等待当前事件完成,并且当前事件等待invokeAndWait完成。 这是一个保证死锁,这就是为什么它不被允许。

你给我们的信息很难说清楚。 然后代码充满了糟糕的编码实践,基本上每隔一行就会引发一些问题。 所以我能做的就是一些复杂的猜测:

第134行不包含任何可能导致锁定的内容,因此必须关闭stacktrace中的信息。 我认为提交是正确的,否则代码将挂起在执行程序创建上,这对于JVM来说非常复杂,不能优化堆栈跟踪。 因此它挂起的行必须是clock.latch()调用。 我不知道它做了什么,但考虑到try / finally结构,它必须是重要的,可能与线程有关。

那么“它为什么会挂起”。 As you already stated two threads try to access the Swing thread for some work, but at least one never returns, which obviously results in a deadlock of all Swing components. To block the Swing thread someone has to at least call it, but no single line in the code presented does that, so again: sophisticated guessing.

The first synchronized statement cannot be the reason, as it has already been passed, the second is not in the stacktrace but considering that this one might be faulty, it could probably be just in the progress of being called thanks to JVM code re-ordering for optimization.

That leaves two candidates for this issue: the one is the clock.latch() , which can cause an issue, but only if it internally does any form of synchronization, for example being declared as synchronized void latch() , although I cannot tell how this would block as there is too little information. But based on the code presented, I assume that the rest of the program is in equal bad shape, so that isn’t this far off. The second possibility is the synchronized(pendingEntries) , but again: there is no evidence in the data presented that could cause this, but given the example anything goes.