我有一个没有响应的应用程序,似乎陷入僵局或类似僵局。请参阅下面的两个主题。请注意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;
}
有谁知道这里发生了什么或如何修复它?
答案 0 :(得分:6)
寻找可信和/或官方来源的答案。
<强> Event Dispatch Thread and EventQueue
强>
Swing事件处理代码在称为事件调度线程(EDT)的特殊线程上运行。大多数调用Swing方法的代码也在这个线程上运行。这是必要的,因为大多数Swing对象方法都不是线程安全的。所有与GUI相关的任务,任何更新都应该在GUI上进行,而绘制过程必须在EDT上进行,包括将请求包装在一个事件中并将其处理到 EventQueue
< / em>的。然后,事件将从一个接一个的队列中按顺序分派,即FIRST IN FIRST OUT。也就是说,如果Event A
在Event B
之前加入EventQueue
,则在事件B
之前不会调度事件A
。
SwingUtilities
类有两个有用的功能来帮助GUI渲染任务:
invokeLater(Runnable)
:导致doRun.run()
在AWT事件派发线程(EDT)上异步执行。如上所述,这将在处理完所有挂起的AWT事件后发生。invokeAndWait(Runnable)
:它与invokeLater
具有相同的功能,但它与invokeLater
的不同之处在于:
invokeAndWait
等待它给EDT的任务,在返回之前完成。WAIT
状态来继续执行它的执行线程。源代码有证据:
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");
}
}
如您所见,我已声明了三个 synchronized 功能:
renderInEDT(final Thread t)
:通过SwingUtilities.invokeAndWait
renderInEDT2(final Thread t)
:通过SwingUtilities.invokeLater
doOtherJob()
:此函数由上述两个函数Runnable
&#39; S run()
方法中的每一个调用。 传递调用线程的引用以检查每个SwingUtilities
函数调用的状态。现在,如果我们在renderInEDT()
的{{1}}实例上调用exmpleClass
:此类ExampleClass
在类上下文中声明:
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()
发布的EDT中执行,因为会出现死锁情况。当SwingUtilities.invokeAndWait
被同步并在一个线程renderInEDT()
内执行时,EDT将需要等待执行t
,直到第一次调用线程完成执行doOtherJob()
方法为止,如official tutorial source of synchronized method
:
无法对两个同步方法进行调用 交错的同一个对象。当一个线程正在执行时 对象的synchronized方法,所有其他调用的线程 同一对象块的同步方法(暂停执行) 直到第一个线程完成对象。
因此,EDT正在等待renderInEDT(final Thread t)
线程完成(和暂停)执行,但线程t
实际上被阻止并通过t
方法发送到等待状态,如上所述因此它无法完成它的执行: EDT的Bth和SwingUtilities.invokeAndWait
线程正在等待彼此执行。
让我们看一下上面的例子:如果我们使用t
发布事件任务,因为如果我们从一个线程在SwingUtilities.invokeLater
实例上执行renderInEDT2()
函数就会很明显:
exampleClass
这次你会看到,函数调用继续正常产生以下输出:
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()
的第一个调用线程执行完毕后由EDT执行:强调我已经让线程进入休眠状态(3s)来检查执行时间因此它显示了状态renderInEDT2()
。
这就解释了你的第二个问题:正如你的一条评论中所说的那样,例外情况也是如此:
enderOnEDT在调用堆栈中以某种方式同步,即 com.acme.persistence.TransactionalSystemImpl.executeImpl方法 是同步的。而renderOnEDT正在等待进入同样的方法。 所以,这就是它看起来像死锁的根源。现在我必须 弄清楚如何解决它。
<强>然而强>,
TIMED_WAITING
特别用于我们想要阻止或等待线程并询问用户确认我们是否应继续使用SwingUtilities.invokeAndWait(Runnable)
等。JOptionPane/JDialogue/JFileChooser
中发布GUI呈现任务,请使用EventQueue
。 SwingUtilities.invokeLater(Runnable)
是用于演示目的,请不要&#39;吨做任何事情,例如,这可能会阻止EDT的时间即使值得一提的量它很少,否则你的Swing会被冻结并迫使你杀死它。Thread.sleep(time)
和{{Runnable
内部1}}功能。此时,您自己应该能够弄清楚并解决您的问题,因为您没有向我们提供足够的详细信息。我认为在您使用像invokeAndWait
这样的同步函数将您的GUI呈现任务发布到事件队列时,正如您在评论中所说的那样,我没有看到任何理由调用另一个 synchronized 从它的invokeLater
开始。而是直接将渲染函数放在此enderOnEDT
中。这是我解释事件队列和EDT机制的唯一目的。
<强>参考:强>
答案 1 :(得分:4)
我认识的Swing开发人员似乎很熟悉invokeAndWait
是有问题的,但也许这并不像我想象的那样众所周知。我似乎记得在文档中看到了关于正确使用invokeAndWait
的困难的严厉警告,但我很难找到任何东西。我在当前的官方文档中找不到任何内容。我唯一能找到的就是来自旧版Swing Tutorial from 2005的这一行:(网络档案)
如果使用
invokeAndWait
,请确保调用invokeAndWait的线程不会保留调用发生时其他线程可能需要的任何锁。
不幸的是,这条线似乎已经从当前的Swing教程中消失了。即便如此,这也是轻描淡写;我更倾向于说出类似的内容,&#34;如果您使用invokeAndWait
,则调用invokeAndWait
的线程不得保留其他线程的任何锁定呼叫正在发生时可能需要。&#34;一般来说,很难知道在任何给定时间内其他线程可能需要锁定什么,最安全的策略可能是确保调用invokeAndWait
的线程根本不保留任何锁定强>
(这很难做到,这就是为什么我在上面说invokeAndWait
是有问题的。我也知道JavaFX的设计者 - 实质上是Swing的替代品 - 在{ {3}}类一个名为runLater
的方法,它在功能上等同于invokeLater
。但是他们故意省略了invokeAndWait
的等效方法,因为它是invokeAndWait
很难正确使用。)
从第一原则推导出来的原因相当简单。考虑一个类似于OP描述的系统,有两个线程:MyThread和Event Dispatch Thread(EDT)。 MyThread锁定对象L,然后调用invokeAndWait
。这会发布事件E1并等待它由EDT处理。假设E1的处理程序需要锁定L.当EDT处理事件E1时,它会尝试锁定L.这个锁已经由MyThread保持,它不会放弃它直到EDT处理E1 ,但该处理被MyThread阻止。因此我们陷入僵局。
以下是此方案的变体。假设我们确保处理E1不需要锁定L.这是否安全?不会。如果在MyThread调用renderOnEDT
之前,事件E0被发布到事件队列,并且E0的处理程序需要锁定L,问题仍然可能发生。如前所述,MyThread持有锁定L ,因此阻止了E0的处理。 E1在事件队列中位于E0后面,因此E1的处理也被阻止。由于MyThread正在等待E1被处理,并且它被E0阻止,而E0被阻止等待MyThread放弃对L的锁定,我们再次陷入僵局。
这听起来与OP的应用程序中发生的情况非常相似。根据OP对javafx.application.Platform的评论,
是的,renderOnEDT在调用堆栈中以某种方式同步,com.acme.persistence.TransactionalSystemImpl.executeImpl方法被同步。而renderOnEDT正在等待进入同样的方法。所以,这就是它看起来像死锁的根源。现在我必须弄清楚如何解决它。
我们没有完整的图片,但这可能已经足够了。正在通过MyThread调用invokeAndWait
,它在invokeAndWait
中阻止了某些内容时锁定了某些内容。它正在等待EDT处理事件,但我们可以看到EDT在MyThread所持有的东西上被阻止。我们无法确切地说出这是什么对象,但它有点无关紧要 - 在MyThread持有的锁上EDT被明显阻止,而MyThread显然正在等待EDT处理事件:因此,死锁。
另请注意,我们可以相当确定EDT目前还没有处理invokeAndWait
发布的事件(类似于上面我的场景中的E1)。如果是的话,每次都会发生僵局。它似乎有时只会发生,并且根据OP this answer上的评论,当用户快速输入时。所以我敢打赌,EDT目前正在处理的事件是在MyThread锁定后发生在事件队列中的键击,但在MyThread调用invokeAndWait
之前将E1发布到事件队列因此,它与我上面的情景中的E0类似。
到目前为止,这可能主要是对问题的回顾,从其他答案和OP对这些答案的评论中拼凑而成。在我们开始讨论解决方案之前,以下是我对OP应用程序的一些假设:
它是多线程的,因此必须同步各种对象才能正常工作。这包括来自Swing事件处理程序的调用,可能会根据用户交互更新某些模型,并且此模型也由工作线程(如MyThread)处理。因此,他们必须正确锁定此类对象。删除同步肯定会避免死锁,但是由于数据结构被非同步的并发访问破坏,其他错误将会蔓延。
应用程序不一定要在EDT上执行长时间运行的操作。这是GUI应用程序的典型问题,但它似乎不会发生在这里。我假设应用程序在大多数情况下工作正常,在EDT上处理的事件抓取锁定,更新某些内容,然后释放锁定。当它无法获得锁定时会出现问题,因为锁定的持有者在EDT上死锁。
将invokeLater
更改为invokeLater
不是一个选项。 OP表示,这样做会导致其他问题。这并不令人惊讶,因为这种变化会导致执行以不同的顺序发生,因此它会产生不同的结果。我认为他们是不可接受的。
如果我们无法删除锁定,而我们无法更改为invokeAndWait
,则我们会安全地拨打synchronized (someObject) {
// code block 1
SwingUtilities.invokeAndWait(handler);
// code block 2
}
。并且&#34;安全&#34;意味着在调用之前放弃锁定。考虑到OP的应用程序的组织,这可能是任意的,但我认为这是唯一的方法。
让我们来看看MyThread正在做什么。这是非常简化的,因为堆栈上可能存在大量的干预方法调用,但从根本上说它是这样的:
someObject
当某个事件在处理程序前面的队列中潜入时,会出现问题,并且该事件的处理需要锁定synchronized
。我们怎样才能避免这个问题?您无法放弃synchronized (someObject) {
// code block 1
}
SwingUtilities.invokeAndWait(handler);
synchronized (someObject) {
// code block 2
}
块内的Java内置监视器锁定之一,因此您必须关闭该块,拨打电话并再次打开它:
someObject
如果invokeAndWait
上的锁定从调用invokeAndWait
调用到调用堆栈的相当远的位置,那么这可能是任意的困难,但我认为进行这种重构是不可避免的。
还有其他陷阱。如果代码块2依赖于代码块1加载的某些状态,则该代码块2再次进行锁定时该状态可能已过期。这意味着代码块2必须从同步对象重新加载任何状态。它不能基于代码块1的结果做出任何假设,因为这些结果可能已经过时。
这是另一个问题。假设由synchronized (someObject) {
// code block 1
SwingUtilities.invokeAndWait(handler(state1, state2));
// code block 2
}
运行的处理程序需要从共享对象加载某些状态,例如
invokeAndWait
您无法将int localState1;
String localState2;
synchronized (someObject) {
// code block 1
localState1 = state1;
localState2 = state2;
}
SwingUtilities.invokeAndWait(handler(localState1, localState2));
synchronized (someObject) {
// code block 2
}
调用从同步块迁移出来,因为这需要获取state1和state2的非同步访问权限。您需要做的是在锁定内将此状态加载到局部变量中,然后在释放锁定后使用这些局部变量进行调用。类似的东西:
synchronized (someObject) {
// code block 1
SwingUtilities.invokeAndWait(handler);
// code block 2
}
在释放锁之后进行调用的技术称为 open call 技术。参见Doug Lea, Java中的并发编程(第2版),第2.4.1.3节。在Goetz等人中也对这种技术进行了很好的讨论。 al。, Java Concurrency In Practice ,sec 10.1.4。实际上,10.1节中的所有内容都相当彻底地解决了死锁我高度推荐它。
总之,我相信使用我在上面描述的技术或引用的书籍中,可以正确,安全地解决这个死锁问题。但是,我确信这需要经过仔细的分析和艰难的重组。但是,我没有看到替代方案。
(最后,我应该说,虽然我是Oracle的员工,但这绝不是甲骨文的正式声明。)
<强>更新强>
我想到了一些可能有助于解决问题的潜在重构。让我们重新考虑代码的原始架构:
invokeAndWait
按顺序执行代码块1,处理程序和代码块2。如果我们要将invokeLater
调用更改为invokeAndWait
,则处理程序将在代码块2之后执行。可以很容易地看到该应用程序存在问题。相反,我们如何将代码块2 移动到 synchronized (someObject) {
// code block 1
}
SwingUtilities.invokeAndWait(Runnable {
synchronized (someObject) {
handler();
// code block 2
}
});
中,以便它以正确的顺序执行,但仍然在事件线程上?
invokeAndWait
这是另一种方法。我并不确切地知道传递给invokeAndWait
的处理程序的目的是什么。但它可能需要invokeLater
的一个原因是它从GUI中读取一些信息,然后使用它来更新共享状态。这必须在EDT上,因为它与GUI对象交互,并且invokeAndWait
无法使用,因为它会以错误的顺序发生。这建议在进行其他处理之前调用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
}
以便将信息从GUI读取到临时区域,然后使用此临时区域执行继续处理:
{{1}}
答案 2 :(得分:2)
在没有看到代码的情况下很难分辨,但是从堆栈跟踪中,看起来你正在从事件调度线程中触发某种事务代码。该代码然后启动My-Thread的实例吗?可以阻止EDT在事务代码中等待My-Thread,但My-Thread无法完成,因为它需要EDT。
如果是这种情况,您可以使用SwingUtilities.invokeLater
进行渲染,以便EDT完成事务代码,然后它将呈现更新。或者,您无法从EDT执行交易代码。对于与渲染无关的实际工作,您应该使用SwingWorker来避免在EDT上进行任何繁重的处理。
答案 3 :(得分:1)
在TransactionalSystemImpl实例上,某些线程(我假设My-Thread@101c
)是synchronized
。 UI线程正在尝试输入executeImpl
,但在synchronized
监视器上被阻止,但不能。还在哪里使用TransactionalSystemImpl实例(带synchronized
条目)?可能介于
at com.acme.ui.ViewBuilder.renderOnEDT(ViewBuilder.java:157)
.
.
.
at com.acme.util.Job.run(Job.java:425)
答案 4 :(得分:1)
我怀疑你引用的134行不是真正的134行(可能是陈旧的代码或其他一些问题)。似乎134正在等待监视器,这很可能意味着synchronized(pendingEntries)
,(或clock.latch()
我认为它是某种倒计时锁存器?)
从堆栈跟踪中,AWT事件调度线程正在等待由MyThread持有的监视器。
请检查MyThread堆栈跟踪的代码库。我相信它在pendingEntries
上同步,然后使用invokeAndWait
要求事件调度线程做某事,然后事件调度线程等待pendingEntries
,这导致死锁
有点偏离主题的建议:您的事件调度线程看起来比它应该做的更多。我不认为在事件调度线程中进行那些事务处理等是一个不错的选择。此类操作可能很慢(在这种情况下,甚至会阻止事件调度线程),这将导致UI无响应。
将此类操作拆分为单独的线程/执行程序对我来说似乎是更好的选择。
答案 5 :(得分:1)
如果没有其他死锁,您可以将对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();
}
}
答案 6 :(得分:0)
不允许来自EDT的invokeAndWait并且应该通过异常。但是看看stacktrace它看起来像是因为你正在使用你的包装线程,它允许你调用invokeAndWait,但那不对。将其更改为SwingUtilities.invokeLater应解决此问题。
替代解决方案:如果它值得你也可以查看SwingWroker类的工作线程。这是链接:
http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html
添加一些有关死锁的更多信息: invokeAndWait的Javadoc清楚地提到“这将在处理完所有待处理事件后发生”。这包括当前事件正在调用invokeAndWait。 invokeAndWait将等待当前事件完成,并且当前事件等待invokeAndWait完成。这是一个保证死锁,这就是为什么不允许它。
答案 7 :(得分:0)
很难说你给我们的信息很少。然后代码充满了糟糕的编码实践,基本上每隔一行就会引发一些问题。所以我能做的就是一些复杂的猜测:
第134行不包含任何可能导致锁定的内容,因此必须关闭stacktrace中的信息。我假设提交是正确的,否则代码将挂起执行程序创建,这对于JVM来说非常复杂,不能优化堆栈跟踪。因此,它挂起的行必须是clock.latch()
调用。我不知道它做了什么,但考虑到try / finally结构,它必须是重要的,可能与线程有关。
然后是“为什么会挂起”。正如您已经说过的,两个线程尝试访问Swing线程进行一些工作,但至少有一个永远不会返回,这显然会导致所有Swing组件出现死锁。要阻止Swing线程,有人必须至少调用它,但是所提供的代码中没有一行能够做到这一点,所以再次:复杂的猜测。
第一个同步语句不能成为原因,因为它已经被传递,第二个不在stacktrace中,但考虑到这个可能有问题,它可能只是在被调用的过程中感谢JVM代码重新排序以进行优化。
这留下了两个候选人:一个是clock.latch()
,这可能会导致问题,但只有在内部进行任何形式的同步时,例如被声明为synchronized void latch()
,尽管由于信息太少,我无法判断这会如何阻止。但根据提供的代码,我假设程序的其余部分处于同样糟糕的状态,所以这不是很远。第二种可能性是synchronized(pendingEntries)
,但是又一次:所提供的数据中没有任何证据可以证明这一点,但是给出了一些例子。