我对JLS 12.5的解读使我认为代码示例中的断言永远不会触发 - 但它在我的多线程代码中会发生。 (JLS未在该部分中指定线程)。但是,我的阅读是否正确是不重要的。我希望这一切都是真的。
public class MainWindow extends JFrame {
private final JLabel label;
public MainWindow() {
label = new JLabel();
pack();
setVisible(true);
}
public JLabel getLabel() {
Assert.assertNotNull(label);
return label;
}
}
显而易见的答案是将构造函数的内部包装在同步块中,并标记getter同步。 有更好的方法吗?
FWIW,另一个线程是在junit测试中使用此代码获取对窗口的引用:
private MainWindow findMainWindow() {
for (Frame frame : Frame.getFrames()) {
if (frame instanceof MainWindow) {
return (MainWindow)frame;
}
}
return null;
}
(顺便说一下,我在Mac上运行JDK6)
更新
我甚至尝试过同步它仍然无法正常工作。这是代码:
public class MainWindow extends JFrame {
private final JLabel label;
public MainWindow() {
synchronized(this) {
label = new JLabel();
}
}
public synchronized JLabel getLabel() {
Assert.assertNotNull(label);
return label;
}
}
更新2:
这是修正它的变化:
private MainWindow findMainWindow() throws Exception {
final AtomicReference<MainWindow> window = new AtomicReference<MainWindow>();
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
for (Frame frame : Frame.getFrames()) {
if (frame instanceof MainWindow) {
window.set((MainWindow) frame);
return;
}
}
}
});
return window.get();
}
答案 0 :(得分:1)
无法保证“label”在“getLabel”中具有值。好吧,实际上我猜有几个,但这是解决问题的错误方法。
问题是你有一个实例字段声明,如:
private MainWindow mainWindow;
和某个地方(最好在EventQueue上运行,否则你会有其他麻烦)这样的声明:
mainWindow = new MainWindow();
一旦此语句启动执行,“mainWindow”就会对将放置MainWindow对象的数据的空间进行非空引用。由于困扰所有多线程程序的运气不好,此时在另一个线程中执行以下代码:
MainWindow mw = mainWindow;
if (mw != null) label = mw.getLabel();
你的断言被触发了。然后你的原始线程在构造函数代码运行时非常仔细地锁定。
解决方案:让“mainWindow”变得不稳定。这将迫使编译器在mainWindow获取其值之前确保MainWindow对象已完成。此外,虽然它不应该是必要的,但我喜欢将实例字段的引用保持在绝对最小值并保持尽可能简单,所以我的代码看起来像这样:
private volatile MainWindow mainWindow;
MainWindow mw = new MainWindow();
mainWindow = mw;
在所有类似情况下都这样做。 每当为从多个线程访问的实例或类字段赋值时,请确保所有其他线程都可以看到您分配的内容。
(如果您也可以将“getLabel”调用到EventQueue上,那么您可以忘记所有这些,并且生活在单线程的幸福中。)
答案 1 :(得分:1)
(注意:如果这个答案的一个发起人实际上回答了它,我将取消接受这个答案并接受他们的答案。)
窗框是在EDT之外构建的。针对Swing策略,其中所有访问 - 包括创建 - 应该在EDT上发生。
我现在创建这样的窗口(使用我的EventQueue utility class):
MainWindow ui = EventQueue.invokeAndGet(new Callable() {
public MainWindow call() {
return new MainWindow(...);
}
});
答案 2 :(得分:0)
一种方法可能是将构造函数设为私有,并提供工厂方法来返回新实例。
类似的东西:
public static final Object lockObject = new Object();
public static final MainWindow createWindowInstance() {
synchronized(lockObject) {
MainWindow win = new MainWindow();
return win;
}
}
这是我的头脑,所以代码更多的伪代码。