在 Java 中顺序使用共享变量

Sequential use of shared variable in Java

我正在开发 java GUI 应用程序。在某些事件(例如按钮单击)中,我想从新工作线程中的文件中读取大量数据并将其存储到共享变量中。然后我想从这个工作线程调用 EDT(事件调度线程)并使用数据。

没有并发访问数据 - 只需在一个线程中创建数据,完成后,在另一个线程中使用它。

我不确定从工作线程调用 EDT 是否确保 EDT 将看到更新的数据,或者 EDT 是否可能仅适用于某些旧的兑现版本。

我不想开始使用可变字段和同步集合,因为我的数据结构很复杂 class,而且我不想使用不必要的同步让我的代码变慢。

伪代码示例:

EDT: MyType data;

EDT: start new Worker thread;
Worker: loadDataFromFileInNewThread(data);
Worker: invokeEDT; //using java.awt.EventQueue.invokeLater
EDT: use data;

共享内存的这种用法是否正确,还是我必须强制 EDT 以某种方式使用更新版本?

在工作线程上,调用 java.awt.EventQueue.invokeLater 最终会在将您的 Runnable 加入队列之前锁定 java.awt.EventQueue.pushPopLock。这意味着您的工作人员此时已在该锁上同步。

在 EDT 方面,EDT 在循环中调用 java.awt.EventQueue.getNextEvent 来接收和处理工作。 getNextEvent 在您的 Runnable 出列之前锁定 java.awt.EventQueue.pushPopLock

此时我们知道您的工作线程和 EDT 都在同一个对象上同步,根据 JMM,这会在两个线程之间创建一个内存刷新点。

因此,在您的特定场景中,您不需要额外的同步或 volatile 来保证 EDT 看到您的工作线程创建的最新信息——只要您的工作线程停止在调用 invokeLater 之后修改共享数据(无论如何这都是最佳实践)。

实施细节:Java 的旧版本不使用 java.util.concurrent.locks.Lock,而是在 EventQueue 的方法上使用 synchronized 方法标志。但是,与上述锁定完全相同的规则适用。

最简单的方法是为您的异步加载任务使用 SwingWorker,因为它简化了与 EDT 的同步,并提供了一些用于定期向 EDT 发送更新的挂钩(例如,用于进度报告):

// should only be accessed by the EDT
MyData myData;

class MyDataLoder extends SwingWorker<MyData, Object> {
    @Override
    public MyData doInBackground() {
        // this method runs in a background thread
        // do loading of data here
        // update progress periodically: setProgress(...);

        return loadedData;
    }

    @Override
    protected void done() {
        // called after doInBackground completes
        // runs in EDT
        try {
            // publish your data to the EDT
            myData = get();
        } catch (Exception ignore) { }
    }
}

可以看到,数据是在done()方法中交给EDT的。这个方法是在EDT中调用的,数据从后台线程"pulled"通过get()安全的传到EDT,所以是安全发布到EDT的。

作为一个不错的副作用,您可以将一些进度条绑定到 SwingWorkerprogress 属性 - 这样您就可以显示加载进度。