如果 Swing 模型的 getter 不是线程安全的,你如何处理它们?

2022-09-03 07:33:29

众所周知,更新 Swing GUI 必须仅在 EDT 中完成。较少宣传的是从GUI读取内容必须/也应该在EDT中完成。例如,让我们以ButtonModel的isSelected()方法为例,该方法告诉(例如)ToggleButton的状态(“down”或“up”)。

在我见过的每个示例中,都从主线程或任何线程中自由查询。但是当我查看DefaultButtonModel的实现时,它没有同步,并且值不是易失性的。因此,严格来说,如果从设置垃圾的线程(即用户按下按钮时的EDT)以外的任何其他线程读取垃圾,则可能会返回垃圾。还是我错了?isSelected()isSelected()

我最初想到这一点时,对布洛赫的有效Java中的第66项感到震惊,这个例子:

public class StopThread {
    private static boolean stopRequested;

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while(!stopRequested) i++;
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

与看起来相反,该程序永远不会终止,至少在某些机器上。从主线程更新标志对后台线程不可见。这种情况可以通过同步的getters和setters来修复,或者通过设置标志 。stopRequestedvolatile

所以:

  1. 在 EDT 之外查询 Swing 模型的状态(严格来说)是否是错误的?
  2. 如果没有,怎么会这样?
  3. 如果是,你如何处理它?靠运气,还是靠一些聪明的解决方法?InvokeAndWait?

答案 1
  1. 不,没有错,但与任何跨线程通信一样,如果您决定这样做,您需要确保提供适当的线程安全机制(例如,使用 或 )。例如,我通常编写自己的实现,通常坐在我的业务对象的位置。如果我打算让其他线程查询列表,我将使它成为同步的。同样值得注意的是,我通常不会从其他线程更新;只查询它。synchronizedvolatileTableModelList<X>XCollectionList
  2. 因为它与任何其他多线程应用程序的情况完全相同。
  3. 我通常使集合同步(请参阅 1)。

警告

尽管我上面有答案,但我通常不会直接在EDT之外访问模型。正如 Carl 所提到的,如果通过某个 GUI 操作调用执行更新的操作,则无需执行任何同步,因为 EDT 已经运行了代码。但是,如果我希望执行一些将导致模型被更改的后台处理,我通常会调用a,然后从方法内部(即在EDT上)分配结果。恕我直言,这是更干净的,因为该方法没有副作用。SwingWorkerdoInBackground()done()doInBackground()


答案 2

是的,这是错误的;除非两个线程在同一对象上同步或使用其他一些内存屏障,如易失性,如JMM所定义的,否则一个或另一个线程可以观察到不一致的内存内容。时期。故事结束。违反这一点可能会在某些甚至许多架构上起作用,但它最终会咬住你。

这里的问题是,除了您提供模型的少数例外情况(例如 Adamski 所指的),Swing 代码不会在任何内容上同步。

值得注意的是,JMM(Java Memory Model)在Java 5中已经以非常重要的方式与JSR-133一起发生了变化,因此使用J4和更早的JVM的行为可能会产生与J5及更高版本不同的结果。正如您所期望的那样,修订后的 JMM 得到了显著改进。


推荐