如何在线程存在的情况下保证对象的完整构造

时间:2011-07-08 11:38:52

标签: java multithreading thread-safety

我对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();
}

3 个答案:

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

这是我的头脑,所以代码更多的伪代码。