Java Swing EDT和并发
我只是想知道是否仍然需要确保invokeLater()Runnable的同步性。
我遇到了死锁,需要在保持并发性的同时克服它。
这是一个好代码的例子吗?:
private String text; private void updateText() { SwingUtilities.invokeLater(new Runnable() { public void run() { synchronized(FrameImpl.this) { someLabel.setText(text); } } }); }
对于相当糟糕的示例感到抱歉,但我们必须假设text
正被不同的线程修改,无法注入,并且依赖于正确的值。
这是正确的解决方案,还是通过将同步代码发送到未知的上下文中而无意中造成死锁问题?
谢谢。
更好的解决方案是这样的:
public class Whatever { private String text; private final Object TEXT_LOCK = new Object(); public void setText(final String newText) { synchronized (TEXT_LOCK) { text = newText; } SwingUtilities.invokeLater(new Runnable() { public void run() { someLabel.setText(newText); } }); } public String getText() { synchronized (TEXT_LOCK) { return text; } } }
这将确保如果两个线程同时尝试调用setText
那么它们将不会互相破坏。 第一个线程将设置text
的值,并使用该值将UI更新排入队列。 第二个线程还将设置text
的值并将第二个UI更新排入队列。
最终结果是UI最终将显示最新的文本值,但内部text
变量将立即包含最新值。
几个笔记:
- 使用单独的锁定对象(即
TEXT_LOCK
)意味着您不会在其他地方将代码锁定在Whatever
实例上并且无意中导致死锁。 最好始终严格控制锁定物体。 最好最小化同步块的大小。 - 您可以使整个
setText
方法同步,但需要注意的是它确实使您可能容易受到上述死锁的影响。 - 即使
Strings
是不可变的,也需要同步读取text
的值。 Java内存模型有一些细微之处,意味着您总是需要围绕可由多个线程读取/写入的变量进行同步。
查看Brian Goetz的Java Concurrency in Practice ,深入了解并发的棘手部分(包括内存模型的怪异)。
现在它是正确的,任务的所有输出必须包装在InvokeLater()中,另一个来自BackGround任务的更新GUI的例子在这里
private String text; private void updateText() { synchronized (FrameImpl.this) { SwingUtilities.invokeLater(new Runnable() { public void run() { someLabel.setText(text); } }); } }