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,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; }
有谁知道这里发生了什么或如何解决?
解决方法
If you use
invokeAndWait
,make sure that the thread that calls invokeAndWait does not hold any locks that other threads might need while the call is occurring.
不幸的是,这条线似乎从目前的Swing教程中消失了.即使这是一个轻描淡写;我喜欢这样说:“如果你使用invokeAndWait,调用invokeAndWait的线程在调用发生时不能保持其他线程可能需要的任何锁.”一般来说,很难知道在任何给定时间内其他线程可能需要什么锁,最安全的策略可能是确保线程调用invokeAndWait根本不会保存任何锁.
(这很难做,这就是为什么我上面说的invokeAndWait是有问题的,我也知道JavaFX的设计者 – 基本上是一个Swing替换 – 在javafx.application.Platform类中定义了一个方法,称为runLater,在功能上等同于invokeLater但是它们故意省略了一个等效的方法来调用和调用,因为它很难正确使用.)
原因是从第一个原则得出的相当直接.考虑一个类似于OP描述的系统,有两个线程:MyThread和事件调度线程(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阻塞,这又被阻塞等待MyThread放弃L上的锁定,我们再次出现死锁.
这听起来与OP应用程序中发生的情况非常相似.根据OP对this answer的评论,
Yes,renderOnEDT is synchronized on something way up in the call stack,the com.acme.persistence.TransactionalSystemImpl.executeImpl method which is synchronized. And renderOnEDT is waiting to enter that same method. So,that is the source of the deadlock it looks like. Now I have to figure out how to fix it.
我们没有一个完整的图片,但这可能足以继续. renderOnEDT正在从MyThread中调用,当它在invokeAndWait中被阻塞时,该对象正在对某个东西进行锁定.正在等待EDT的事件处理,但是我们可以看到EDT被MyThread所持有的东西阻挡.我们不能完全明确地说明哪个对象,但这并不重要 – EDT被MyThread所锁定的明确阻止,而MyThread显然正在等待EDT来处理事件:因此,死锁.
还要注意,我们可以肯定的是,EDT目前不处理由invokeAndWait发布的事件(类似于上面我的场景中的E1).如果是这样,每次都会发生僵局.它似乎只发生在有时候,根据07年3月的OP的评论,当用户快速打字时.所以我敢打赌,EDT正在处理的事件是一个按键,恰好在MyThread锁定之后被发布到事件队列,但在MyThread调用invokeAndWait之前将E1发布到事件队列中,因此它类似于E0在我上面的场景中
到目前为止,这可能大概是从其他答案和OP对这些答案的意见拼合在一起的问题的回顾.在我们继续讨论一个解决方案之前,下面是我对OP应用程序的一些假设:
>它是多线程的,所以各种对象必须同步才能正常工作.这包括来自Swing事件处理程序的调用,这可能是基于用户交互更新某些模型,并且此模型也由诸如MyThread之类的工作线程处理.因此,他们必须正确地锁定这些物体.删除同步绝对会避免死锁,但其他错误会随着数据结构被不同步的并发访问所破坏而蔓延.
>应用程序不一定在EDT上执行长时间运行的操作.这是GUI应用程序的典型问题,但在这里似乎没有发生.我假设应用程序在大多数情况下工作正常,其中在EDT处理的事件抓住锁,更新某些东西,然后释放锁定.由于锁的持有者在美国东部时间(ICE)被锁死,因此无法获得锁定时出现此问题.
>将invokeAndWait更改为invokeLater不是一个选项.操作程序表示这样做会导致其他问题.这并不奇怪,因为这种变化导致执行以不同的顺序发生,所以它将给出不同的结果.我会假设他们是不能接受的.
如果我们无法删除锁,并且我们无法更改为invokeLater,那么我们可以安全地调用invokeAndWait.而“安全”是指在拨打之前放开锁.由于OP的申请的组织,这可能是任意的,但我认为这是唯一的办法.
我们来看看MyThread正在做什么.这很简单,因为堆栈上可能有一堆介入的方法调用,但从根本上来说,它是这样的:
synchronized (someObject) { // code block 1 SwingUtilities.invokeAndWait(handler); // code block 2 }
当一些事件在处理程序前面的队列中潜移默化时,会出现此问题,该事件的处理需要锁定someObject.我们如何避免这个问题呢?您不能放弃同步块中的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调用从同步块中迁移出来,因为这将需要不同步的访问获取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并发实践,第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的一个原因是它从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 }