简体   繁体   English

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

[英]How do I guarantee the complete construction of an object in the presence of threads

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.我对JLS 12.5的阅读让我认为代码示例中的断言永远不应该触发——但它在我的多线程代码中会触发。 (The JLS doesn't specify threading in the section). (JLS 没有在该部分中指定线程)。 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.显而易见的答案是将构造函数的内部组件包装在同步块中,并将 getter 标记为同步。 Is there a better way?有没有更好的办法?

FWIW, the other thread is getting a reference to the window with this code inside a junit test: FWIW,另一个线程在 junit 测试中使用此代码获得对 window 的引用:

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) (顺便说一句,我在 Mac 上运行 JDK6)

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:更新 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();
}

There is no way to guarantee "label" has a value in "getLabel".无法保证“label”在“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:问题是您在某处有一个实例字段声明,例如:

private MainWindow  mainWindow;

and somewhere (and it better run on the EventQueue, or you will have other troubles) a statement like:在某处(最好在 EventQueue 上运行,否则您将遇到其他麻烦)如下语句:

mainWindow = new MainWindow();

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.一旦该语句开始执行,“mainWindow”将对放置 MainWindow object 数据的空间进行非空引用。 Due to the bad luck that plagues all multi-threaded programs, at this point in another thread the following code gets executed:由于困扰所有多线程程序的厄运,此时在另一个线程中执行以下代码:

MainWindow  mw = mainWindow;
if (mw != null)  label = mw.getLabel();

Your assertion is triggered.你的断言被触发了。 Then your original thread very thoughtfully locks while the constructor code runs.然后,当构造函数代码运行时,您的原始线程会非常周到地锁定。

The Solution: Make "mainWindow" a volatile.解决方案:将“mainWindow”设置为 volatile。 That will force the compiler to be sure the MainWindow object is completed before mainWindow gets its value.这将强制编译器确保 MainWindow object 在 mainWindow 获得其值之前完成。 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:此外,虽然这不是必需的,但我希望将对实例字段的引用保持在绝对最低限度并使其尽可能简单,因此我的代码如下所示:

private volatile MainWindow  mainWindow;

MainWindow  mw = new MainWindow();
mainWindow = mw;

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.每当您为从多个线程访问的实例或 class 字段分配值时,请确保您分配的内容已准备好被所有其他线程看到。

(If you can get the call to "getLabel" onto the EventQueue also, you can forget about all this and live in single-threaded bliss.) (如果你也可以在 EventQueue 上调用“getLabel”,你就可以忘记这一切,享受单线程的幸福。)

(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. window 框架正在 EDT 之外构建。 This against Swing policy where all access—including creation—should happen on the EDT.这违反了 Swing 策略,其中所有访问(包括创建)都应在 EDT 上进行。

I now create the window like this (using my EventQueue utility class ):我现在像这样创建 window (使用我的EventQueue 实用程序 class ):

MainWindow ui = EventQueue.invokeAndGet(new Callable() {
    public MainWindow call() {
        return new MainWindow(...);
    }
});

One way might be to make the contructor private and provide a factory method to return a new instance.一种方法可能是将构造函数设为私有并提供工厂方法来返回新实例。

Something like:就像是:

public static final Object lockObject = new Object();
public static final MainWindow createWindowInstance() {
  synchronized(lockObject) {
    MainWindow win = new MainWindow();
    return win;
  }
}

This is off the top of my head so the code is more psuedo code.这不是我的想法,所以代码是更多的伪代码。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM