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

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

问题描述

我有一个没有反应的应用程序,似乎是一个死锁或者像一个死锁。请参见下面的两个线程。请注意, My-Thread @ 101c 线程阻塞 AWT-EventQueue-0 @ 301 。但是, My-Thread 刚刚调用 java.awt.EventQueue.invokeAndWait()。所以 AWT-EventQueue-0 阻止我的线程(我相信)。

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 的困难的严重警告,但我很难找到任何东西。我在当前的官方文档中找不到任何东西。我唯一能找到的是这一行从旧版本的 Swing Tutorial from 2005 :(web archive)

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 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调用,它在某事上持有锁,而在 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对此答案的评论,当用户快速输入时。所以我打赌,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处理的事件获取锁,更新某事,然后释放锁。

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 c $ c>到 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 。我们如何避免这个问题?您不能在同步块中放弃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
}


b $ b

如果 someObject 上的锁在从调用 invokeAndWait 的调用堆栈上相当远的位置, code>,但我认为这种重构是不可避免的。

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 调用synchronized块,因为这将需要获取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
}


b $ b

在释放锁之后进行调用的技术称为打开调用技术。见Doug Lea, Concurrent Programming in Java (第2版),第2.4.1.3节。在Goetz等人中也有对这种技术的良好讨论。 al。, Java Concurrency In Practice ,第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.)

UPDATE

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

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
}

,和码块2。如果我们改变 invokeAndWait 调用 invokeLater ,处理程序将在代码块2之后执行。看到这将是一个应用程序的问题。相反,我们如何将代码块2 移动到 css> invokeAndWait 中,以便它以正确的顺序执行,但仍然在事件线程上执行? p>

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天全站免登陆