在存在线程的情况下如何保证对象的完整构造
我对 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();
}
My reading of JLS 12.5 makes me think the assertion in the code sample should never trigger—but it does in my multithreaded code. (The JLS doesn't specify threading in the section). But whether or not my reading is correct is beside the point. I want to get this to always be true.
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;
}
}
The obvious answer is to wrap the innards of the constructor in a synchronized block and to mark the getter synchronized as well. Is there a better way?
FWIW, the other thread is getting a reference to the window with this code inside a junit test:
private MainWindow findMainWindow() {
for (Frame frame : Frame.getFrames()) {
if (frame instanceof MainWindow) {
return (MainWindow)frame;
}
}
return null;
}
(BTW, I'm running JDK6 on a Mac)
Update:
I even tried synchronizing it and it still doesn't work. Here's the code:
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;
}
}
Update 2:
Here's the change that fixed it:
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();
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
无法保证“label”在“getLabel”中具有值。嗯,实际上我猜有几个,但这是解决问题的错误方法。
问题是,某个地方有一个实例字段声明,例如:
和某个地方(最好在 EventQueue 上运行,否则会遇到其他麻烦)这样的语句:
一旦此语句开始执行,“ mainWindow”对放置 MainWindow 对象数据的空间有一个非空引用。由于困扰所有多线程程序的厄运,此时在另一个线程中执行以下代码:
您的断言被触发。然后,在构造函数代码运行时,您的原始线程会经过深思熟虑地锁定。
解决方案:使“mainWindow”成为易失性的。这将迫使编译器确保 MainWindow 对象在 mainWindow 获取其值之前完成。另外,虽然没有必要,但我喜欢将对实例字段的引用保持在绝对最低限度,并使其尽可能简单,因此我的代码如下所示:
在所有类似情况下执行此操作。 每当为从多个线程访问的实例或类字段分配值时,请确保所有其他线程都可以看到您分配的内容。
(如果您也可以在 EventQueue 上调用“getLabel”,您就可以忘记这一切并生活在单线程的幸福中。)
There is no way to guarantee "label" has a value in "getLabel". Well, actually I guess there are several, but that's the wrong way to approach the problem.
The problem is that somewhere you have an instance field declaration like:
and somewhere (and it better run on the EventQueue, or you will have other troubles) a statement like:
Once this statement starts to execute, "mainWindow" has a non-null reference to the space where the data for a MainWindow object will be placed. Due to the bad luck that plagues all multi-threaded programs, at this point in another thread the following code gets executed:
Your assertion is triggered. Then your original thread very thoughtfully locks while the constructor code runs.
The Solution: Make "mainWindow" a volatile. That will force the compiler to be sure the MainWindow object is completed before mainWindow gets its value. Also, while it should not be necessary, I like to keep references to instance fields to an absolute minimum and to keep them as simple as possible, so I'd have the code looking like this:
Do this in all similar cases. Whenever you assign a value to an instance or class field accessed from more than one thread, make sure what you are assigning is ready to be seen by all other threads.
(If you can get the call to "getLabel" onto the EventQueue also, you can forget about all this and live in single-threaded bliss.)
(注意:如果这个答案的发起者之一实际上回答了这个问题,我将不接受这个答案并接受他们的答案。)
窗框是根据 EDT 建造的。这违反了 Swing 策略,其中所有访问(包括创建)都应该在 EDT 上进行。
我现在创建这样的窗口(使用我的 EventQueue 实用程序类):
(Note: if one of the originators of this answer actually answers it, I will un-accept this answer and accept theirs instead.)
The window frame is being constructed off the EDT. This against Swing policy where all access—including creation—should happen on the EDT.
I now create the window like this (using my EventQueue utility class):
一种方法可能是将构造函数设为私有并提供工厂方法来返回新实例。
就像这样:
这超出了我的想象,所以代码更多是伪代码。
One way might be to make the contructor private and provide a factory method to return a new instance.
Something like:
This is off the top of my head so the code is more psuedo code.