在Java中顺序使用共享变量

时间:2015-05-06 16:49:24

标签: java multithreading swing shared-memory event-dispatch-thread

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

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

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

我不想使用易失性字段和同步集合,因为我的数据类结构复杂,而且我不想使用不必要的同步使代码变慢。

伪代码示例:

EDT: MyType data;

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

共享内存的使用是否正常,或者我是否必须强制EDT以某种方式使用更新版本?

2 个答案:

答案 0 :(得分:2)

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

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

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

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

实现细节:较早版本的Java不使用java.util.concurrent.locks.Lock,而是使用EventQueue方法上的synchronized方法标志。但是,完全相同的规则适用于上述Lock。

答案 1 :(得分:0)

最简单的方法是使用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中调用此方法,数据通过get()安全地从后台线程“拉”到EDT,因此可以安全地发布到EDT。

作为一个很好的副作用,您可以将一些进度条绑定到progress的{​​{1}}属性 - 这样您就可以显示加载进度。