涉及 Swing 和 AWT-EventQueue 的无响应线程 [英] Unresponsive threading involving Swing and AWT-EventQueue

查看:34
本文介绍了涉及 Swing 和 AWT-EventQueue 的无响应线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个应用程序没有响应,似乎处于死锁或类似死锁的状态.请参阅下面的两个线程.请注意,My-Thread@101c 线程阻塞了 AWT-EventQueue-0@301.但是,My-Thread 刚刚调用了 java.awt.EventQueue.invokeAndWait().所以 AWT-EventQueue-0 阻塞了 My-Thread(我相信).

I have an application that is unresponsive and seems to be in a deadlock or something like a deadlock. See the two threads below. Notice that the My-Thread@101c thread blocks AWT-EventQueue-0@301. However, My-Thread has just called java.awt.EventQueue.invokeAndWait(). So AWT-EventQueue-0 blocks My-Thread (I believe).

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;
}

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

Does anyone know what's going on here or how to fix it?

推荐答案

我认识的 Swing 开发人员似乎都知道 invokeAndWait 是有问题的,但也许这不是众所周知的正如我所想的那样.我似乎记得在文档中看到过关于正确使用 invokeAndWait 困难的严厉警告,但我很难找到任何东西.我在当前的官方文档中找不到任何内容.我唯一能找到的是旧版本的 2005 年的 Swing 教程:(网络存档)

It seems to be well known among Swing developers of my acquaintance that invokeAndWait is problematic, but maybe this isn't as well known as I had thought. I seem to recall having seen stern warnings in the documentation about difficulties in using invokeAndWait properly, yet I'm having a hard time finding anything. I cannot find anything in current, official documentation. The only thing I've been able to find is this line from an old version of the Swing Tutorial from 2005: (web archive)

如果您使用 invokeAndWait,请确保调用 invokeAndWait 的线程不持有在调用发生时其他线程可能需要的任何锁.

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 的线程根本不持有任何锁.

Unfortunately this line seems to have disappeared from the current Swing tutorial. Even this is rather an understatement; I'd have preferred that it say something like, "If you use invokeAndWait, the thread that calls invokeAndWait must not hold any locks that other threads might need while the call is occurring." In general it's difficult to know what locks other threads might need during any given time, the safest policy is probably to ensure that the thread calling invokeAndWait doesn't hold any locks at all.

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

(This is pretty difficult to do, and it's why I said above that invokeAndWait is problematic. I also know that the designers of JavaFX -- essentially a Swing replacement -- defined in the javafx.application.Platform class a method called runLater which is functionally equivalent to invokeLater. But they deliberately omitted an equivalent method to invokeAndWait because it's very difficult to use properly.)

从第一性原理推导出来的原因相当简单.考虑一个类似于 OP 描述的系统,它有两个线程:MyThread 和事件调度线程 (EDT).MyThread 锁定对象 L,然后调用 invokeAndWait.这会发布事件 E1 并等待 EDT 对其进行处理.假设 E1 的处理程序需要锁定 L.当 EDT 处理事件 E1 时,它尝试获取 L 上的锁.这个锁已经被 MyThread 持有,它不会放弃它,直到 EDT 处理 E1,但该处理被阻塞通过 MyThread.因此我们陷入了僵局.

The reason is fairly straightforward to derive from first principles. Consider a system similar to the one described by the OP, having two threads: MyThread and the Event Dispatch Thread (EDT). MyThread takes a lock on object L and then calls invokeAndWait. This posts event E1 and waits for it to be processed by the EDT. Suppose that E1's handler needs to lock L. When the EDT processes event E1, it attempts to take the lock on L. This lock is held already by MyThread, which won't relinquish it until the EDT processes E1, but that processing is blocked by MyThread. Thus we have deadlock.

这是此场景的一个变体.假设我们确保处理 E1 不需要锁定 L.这会安全吗?不.如果就在 MyThread 调用 invokeAndWait 之前,将事件 E0 发布到事件队列,并且 E0 的处理程序需要锁定 L,问题仍然会发生.和以前一样,MyThread 持有 L 上的锁定,所以 E0 的处理被阻塞.E1 在事件队列中位于 E0 之后,因此 E1 的处理也被阻止.由于 MyThread 正在等待 E1 被处理,它被 E0 阻塞,而 E0 又被阻塞等待 MyThread 释放对 L 的锁,我们再次陷入死锁.

Here's a variation on this scenario. Suppose we ensure that processing E1 doesn't require locking L. Will this be safe? No. The problem can still occur if, just before MyThread calls invokeAndWait, an event E0 is posted to the event queue, and E0's handler requires locking on L. As before, MyThread holds the lock on L, so processing of E0 is blocked. E1 is behind E0 in the event queue so processing of E1 is blocked too. Since MyThread is waiting for E1 to be processed, and it's blocked by E0, which in turn is blocked waiting for MyThread to relinquish the lock on L, we have deadlock again.

这听起来与 OP 应用程序中发生的事情非常相似.根据 OP 对 this answer 的评论,

This sounds fairly similar to what's going on in the OP's application. According to the OP's comments on this answer,

是的,renderOnEDT 是在调用堆栈中以某种方式同步的,即同步的 com.acme.persistence.TransactionalSystemImpl.executeImpl 方法.而 renderOnEDT 正在等待进入相同的方法.所以,这就是它看起来像的僵局的根源.现在我必须弄清楚如何修复它.

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 调用,MyThread 持有锁,同时它在 invokeAndWait 中被阻塞.它正在等待 EDT 处理一个事件,但我们可以看到 EDT 被 MyThread 持有的某些东西阻塞了.我们不能确切地说出这是哪个对象,但这并不重要——EDT 显然被 MyThread 持有的锁阻塞了,而且 MyThread 显然正在等待 EDT 处理一个事件:因此,死锁.

We don't have a complete picture, but this is probably enough to go on. renderOnEDT is being called from MyThread, which is holding a lock on something while it's blocked in invokeAndWait. It's waiting for an event to be processed by the EDT, but we can see the EDT is blocked on something held by MyThread. We can't quite tell exactly which object this is, but it kind of doesn't matter -- the EDT is clearly blocked on a lock held by MyThread, and MyThread is clearly waiting for the EDT to process an event: thus, deadlock.

还要注意,我们可以相当确定 EDT 当前没有处理 invokeAndWait 发布的事件(类似于我上面场景中的 E1).如果是这样,每次都会发生死锁.根据 OP 对 this answer 的评论,当用户快速输入时,它似乎只发生在有时.所以我敢打赌 EDT 当前正在处理的事件是在 MyThread 获取锁定之后碰巧发布到事件队列的击键,但在 MyThread 调用 invokeAndWait 将 E1 发布到事件之前队列,因此它类似于我上面场景中的 E0.

Note also that we can be fairly sure the EDT isn't currently processing the event posted by invokeAndWait (analogous to E1 in my scenario above). If it were, the deadlock would occur every time. It seems to occur only sometimes, and according to a comment from the OP on this answer, when the user is typing quickly. So I'd bet that the event currently being processed by the EDT is a keystroke that happened to be posted to the event queue after MyThread took its lock, but before MyThread called invokeAndWait to post E1 to the event queue, thus it's analogous to E0 in my scenario above.

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

So far, this is probably mostly a recap of the problem, pieced together from other answers and from the OP's comments on those answers. Before we proceed to talking about a solution, here are some assumptions I'm making about the OP's application:

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

  • It's multi-threaded, so various objects must be synchronized to work properly. This includes calls from Swing event handlers, which presumably update some model based on user interaction, and this model is also processed by worker threads such as MyThread. Therefore, they must lock such objects properly. Removing synchronization will definitely avoid deadlocks, but other bugs will creep in as the data structures are corrupted by unsynchronized concurrent access.

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

The application isn't necessarily performing long-running operations on the EDT. This is a typical problem with GUI apps but it doesn't seem to be happening here. I'm assuming that the application works fine in most cases, where an event processed on the EDT grabs a lock, updates something, then releases the lock. The problem occurs when it can't get the lock because the lock's holder is deadlocked on the EDT.

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

Changing invokeAndWait to invokeLater isn't an option. The OP has said that doing so causes other problems. This isn't surprising, as that change causes execution to occur in a different order, so it will give different results. I'll assume they would be unacceptable.

如果我们无法移除锁,并且无法更改为 invokeLater,那么我们只能安全地调用 invokeAndWait.安全"意味着在调用它之前放弃锁定.考虑到 OP 应用程序的组织,这可能很难做到,但我认为这是继续进行的唯一方法.

If we can't remove locks, and we can't change to invokeLater, we're left with calling invokeAndWait safely. And "safely" means relinquishing locks before calling it. This might be arbitrarily hard to do given the organization of the OP's application, but I think it's the only way to proceed.

让我们看看 MyThread 正在做什么.这简化了很多,因为堆栈上可能有一堆中间方法调用,但基本上是这样的:

Let's look at what MyThread is doing. This is much simplified, as there are probably a bunch of intervening method calls on the stack, but fundamentally it's something like this:

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

当某些事件潜入处理程序前面的队列时,就会出现问题,并且该事件的处理需要锁定someObject.我们怎样才能避免这个问题?您不能在 synchronized 块中放弃 Java 的内置监视器锁之一,因此您必须关闭该块,进行调用,然后再次打开它:

The problem occurs when some event sneaks in the queue in front of handler, and that event's processing requires locking someObject. How can we avoid this problem? You can't relinquish one of Java's built-in monitor locks within a synchronized block, so you have to close the block, make your call, and open it again:

synchronized (someObject) {
    // code block 1
}

SwingUtilities.invokeAndWait(handler);

synchronized (someObject) {
    // code block 2
}

如果 someObject 上的锁在调用 invokeAndWait 的调用堆栈中占据相当远的位置,这可能会非常困难,但我认为进行这种重构是不可避免的.

This could be arbitrarily difficult if the lock on someObject is taken fairly far up the call stack from the call to invokeAndWait, but I think doing this refactoring is unavoidable.

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

There are other pitfalls, too. If code block 2 depends on some state loaded by code block 1, that state might be out of date by the time code block 2 takes the lock again. This implies that code block 2 must reload any state from the synchronized object. It mustn't make any assumptions based on results from code block 1, since those results might be out of date.

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

Here's another issue. Suppose the handler being run by invokeAndWait requires some state loaded from the shared object, for example,

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

您不能仅将 invokeAndWait 调用迁移出同步块,因为这需要非同步访问以获取 state1 和 state2.相反,您必须做的是在锁定内将此状态加载到局部变量中,然后在释放锁定后使用这些局部变量进行调用.类似的东西:

You couldn't just migrate the invokeAndWait call out of the synchronized block, since that would require unsynchronized access getting state1 and state2. What you have to do instead is to load this state into local variables while within the lock, then make the call using those locals after releasing the lock. Something like:

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 等人也对这种技术进行了很好的讨论.al.,Java 并发实践,第 10.1.4 节.事实上,第 10.1 节的所有内容都相当彻底地涵盖了死锁;我强烈推荐它.

The technique of making calls after having released locks is called the open call technique. See Doug Lea, Concurrent Programming in Java (2nd edition), sec 2.4.1.3. There is also a good discussion of this technique in Goetz et. al., Java Concurrency In Practice, sec 10.1.4. In fact all of section 10.1 covers deadlock fairly thoroughly; I recommend it highly.

总而言之,我相信使用我上面描述的技术或引用的书籍中的技术,将正确且安全地解决这个死锁问题.但是,我确信这也需要大量仔细的分析和艰难的重组.不过,我看不到替代方案.

In summary, I believe that using techniques I describe above, or in the books cited, will solve this deadlock problem correctly and safely. However, I am sure that it will require a lot of careful analysis and difficult restructuring as well. I don't see an alternative, though.

(最后,我应该说,虽然我是 Oracle 的员工,但这绝不是 Oracle 的官方声明.)

(Finally, I should say that while I am an employee of Oracle, this is not in any way an official statement of Oracle.)

更新

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

I thought of a couple more potential refactorings that might help solve the problem. Let's reconsider the original schema of the code:

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

这将依次执行代码块 1、处理程序和代码块 2.如果我们将 invokeAndWait 调用更改为 invokeLater,处理程序将在代码块 2 之后执行.很容易看出这对应用程序来说是一个问题.相反,我们如何将代码块 2 移入 invokeAndWait 以便它以正确的顺序执行,但仍然在事件线程上?

This executes code block 1, handler, and code block 2 in order. If we were to change the invokeAndWait call to invokeLater, the handler would be executed after code block 2. One can easily see that would be a problem for the application. Instead, how about we move code block 2 into the invokeAndWait so that it executes in the right order, but still on the event thread?

synchronized (someObject) {
    // code block 1
}

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

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

Here's another approach. I don't know exactly what the handler passed to invokeAndWait is intended to do. But one reason it might need to be invokeAndWait is that it reads some information out of the GUI and then uses this to update the shared state. This has to be on the EDT, since it interacts with GUI objects, and invokeLater can't be used since it would occur in the wrong order. This suggests calling invokeAndWait before doing other processing in order to read information out of the GUI into a temporary area, then use this temporary area to perform continued processing:

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
}

这篇关于涉及 Swing 和 AWT-EventQueue 的无响应线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆