Java 如果是Swing型号';吸气剂不是';t线程安全,如何处理它们?
众所周知,Swing GUI的更新必须仅在EDT中完成。很少有人宣称,从GUI读取内容也必须/应该在EDT中完成。例如,让我们使用方法,它告诉(例如)ToggleButton的状态(“向下”或“向上”) 在我看到的每个示例中,Java 如果是Swing型号';吸气剂不是';t线程安全,如何处理它们?,java,swing,thread-safety,Java,Swing,Thread Safety,众所周知,Swing GUI的更新必须仅在EDT中完成。很少有人宣称,从GUI读取内容也必须/应该在EDT中完成。例如,让我们使用方法,它告诉(例如)ToggleButton的状态(“向下”或“向上”) 在我看到的每个示例中,isSelected()都是从主线程或任意线程自由查询的。但是,当我查看DefaultButtonModel的实现时,它不是同步的,并且值也不是易变的。因此,严格地说,isSelected()可能会返回垃圾,如果它是从设置它的线程(当用户按下按钮时是EDT)之外的任何其他线
isSelected()
都是从主线程或任意线程自由查询的。但是,当我查看DefaultButtonModel的实现时,它不是同步的,并且值也不是易变的。因此,严格地说,isSelected()
可能会返回垃圾,如果它是从设置它的线程(当用户按下按钮时是EDT)之外的任何其他线程读取的。还是我错了
当我被Bloch的高效Java中的#66项震惊时,我最初想到了这一点,例如:
public class StopThread {
private static boolean stopRequested;
public static void main(String[] args) throws InterruptedException {
Thread backgroundThread = new Thread(new Runnable() {
public void run() {
int i = 0;
while(!stopRequested) i++;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested = true;
}
}
与看起来的相反,该程序从未终止,至少在某些机器上是如此。从主线程更新stopRequested
标志对后台线程不可见。这种情况可以通过同步getter和setter来修复,或者通过设置标志volatile
因此:
同步
或易失性
)。例如,我通常编写自己的TableModel
实现,通常位于List
上,其中X
是我的业务对象。如果我想让其他线程查询该列表,我将使其成为一个同步的集合
。同样值得注意的是,我通常不会从其他线程更新列表
;只需查询它尽管我给出了上述答案,但我通常不会直接在EDT之外访问模型。正如Carl提到的,如果执行更新的操作是通过某个GUI操作调用的,则无需执行任何同步,因为代码已经由EDT运行。但是,如果我希望执行一些导致模型更改的后台处理,我通常会调用
SwingWorker
,然后从done()
方法(即在EDT上)中分配doInBackground()
的结果。这是一种更干净的方法,因为doInBackground()
方法没有副作用。是的,它是错误的;除非两个线程同步同一对象上的,或使用JMM定义的其他内存屏障(如volatile),否则其中一个线程可能观察到不一致的内存内容。时期故事结束了。违反这一点可能会在某些甚至许多体系结构上起作用,但它最终会在hiney上咬你一口
这里的问题是,除了提供模型的少数例外情况,比如Adamski所提到的,Swing代码不会在任何东西上同步
值得注意的是,JMM(Java内存模型)在Java5中的JSR-133中发生了非常重要的变化,因此J4和早期JVM的行为可能会产生与J5和更高版本不同的结果。正如您所期望的,修订后的JMM有了很大的改进。这是一个很好的问题,软件Monkey给出了正确的答案。如果你想做有效的线程,你必须完全理解(JMM)。请注意,java内存模型在JSR-133中发生了变化,因此您发现的许多旧线程示例都是错误的。例如,volatile关键字已从几乎无用变为现在几乎与synchronized关键字一样强制执行 此外,我们现在有真正的、无处不在的多核cpu,这意味着真正的多线程。直到几年前,多线程还只是在单核cpu上伪造的,这就是为什么有这么多糟糕的示例代码。它过去是有用的。现在不会了 说真的,如果约书亚·布洛赫(Joshua Bloch)弄错了,那就小心点。这是一件复杂的事情(是的,他的代码是错误的。让这个变量不稳定,它在所有情况下都能工作) 哦,是的,亚当斯基说得对。使用
SwingWorker
确保长时间运行的代码是在后台线程中完成的,并且在处理完数据后,它应该通过done()
方法访问Swing。如果您需要阅读,请在制作您的SwingWorker
之前阅读,并将信息制作成最终版本
在EDT上调用的代码示例,最有可能在ActionListener或类似的程序中调用(psuedocode):
Swing通常不仅是线程安全的,而且是线程敌对的。然而,除了Swing文本之外,大多数模型都是线程无关的。这意味着您可以在任何线程中使用模型,只要您使用标准线程保护。Swing文本是一种线程安全的尝试,但失败了,事实上它是线程敌对的。仅使用事件分派线程(EDT)中的
文档
s
通常的建议是运行潜在的lo
public void someOverlySimpleExampleThatIsCalledOnEDT() {
/*
* Any code run here is guaranteed to happen-before
* the start of a background thread.
*/
final String text = myTextField().getText();
SwingWorker sw = new SwingWorker() {
private volatile List data;
public Object doInBackground() {
//happens in background thread. No Swing access
data = myDao.getDataFromInternet(text);
return null;
}
public void done() {
//Happens in EDT. Swing away Merrill
loadDataIntoJTable(data);
}
}
sw.execute();
/*
* Any code run here happens concurrently with doInBackground(). Be careful.
* Usually leave empty
*/
}
public static class ThreadUnsafeAccessException extends RuntimeException {
private static final long serialVersionUID = 1L;
}
@Override public Object getElementAt(int index) {
if (!SwingUtilities.isEventDispatchThread()) {
throw new ThreadUnsafeAccessException();
}
return decorated.getElementAt(index);
}