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

时间:2010-01-19 08:19:54

标签: java swing thread-safety

众所周知,更新Swing GUI必须在EDT中完成。较少的广告是来自GUI的读取内容必须/也应该在EDT中完成。例如,让我们采用ButtonModel's isSelected()方法,它告诉(例如)ToggleButton的状态(“向下”或“向上”)。

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

在Bloch的Effective 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;
    }
}

与看起来相反,该程序永远不会终止,至少在某些机器上。从主线程更新stopRequested标志对后台线程是不可见的。这种情况可以通过同步的getter& amp;设置者,或设置标志volatile

所以:

  1. 在EDT之外查询Swing模型的状态(严格来说)是错误的吗?
  2. 如果没有,怎么回事?
  3. 如果是,您如何处理?运气好,还是通过一些聪明的解决方法? InvokeAndWait?

6 个答案:

答案 0 :(得分:4)

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

    尽管我上面的回答,我通常直接在EDT之外访问模型。正如Carl所提到的,如果通过某些GUI动作调用执行更新的动作,则不需要执行任何同步,因为EDT已经运行了代码。但是,如果我希望执行一些导致模型被更改的后台处理,我通常会调用SwingWorker然后从doInBackground()方法中分配done()的结果(即在美国东部时间)。这是更清洁的恕我直言,因为doInBackground()方法没有副作用。

答案 1 :(得分:4)

是的,这是错的;除非两个线程同步 在同一个对象上或使用其他内存屏障,如volatile,如JMM 所定义,否则可以观察到不一致的内存内容。期。故事结局。违反这一规定可能适用于某些甚至许多架构,但它最终会让你陷入困境。

这里的问题是,除了您提供模型的一些例外情况,例如Adamski所提到的,Swing代码不会同步任何内容。

值得注意的是JMM(Java内存模型)在Java 5中已经以非常重要的方式改变了JSR-133,因此使用J4和早期JVM的行为可能会产生与J5及更高版本不同的结果。正如您所料,修订后的JMM大大改善了。

答案 2 :(得分:2)

Swing通常不仅不是线程安全的,而且是线程敌对的。但是,除了Swing文本之外,大多数模型都是线程无关的。这意味着您可以在任何线程中使用模型,前提是您使用标准线程保护。 Swing文本试图是线程安全的但是失败了,实际上是线程敌对的。仅使用事件调度线程(EDT)中的Document

通常的建议是在EDT上运行可能长时间运行(通常是阻塞)的任务。但是,线程很难。 SwingWorker是引发糟糕设计的流行方式 - 避免它。尽量保持EDT和非EDT代码分开。尽量不要分享可变状态。

线程错误很难。您可能无法在计算机上看到它们,但客户可能会这样做。由于JRE的更高版本中的优化,可能会出现错误。线程错误很难跟踪。因此,要保守。即使短暂地阻止EDT也比讨厌的bug更好。

答案 3 :(得分:1)

  1. 我从不担心,但严格地说,是的,这可能是错的。
  2. N / A
  3. 我更改了我自己构造的GUI更新(通过侦听器)模型,这些模型不是由Swing包含或构造的。由于GUI更新了模型,因此无需询问GUI。
  4. 由于我自己创建模型(这可能非常简单,例如带有getter / setter和PropertyChangeSupport的String),如果需要,我可以使访问器同步。由于线程争用等原因,我很少遇到片状行为。

答案 4 :(得分:1)

  

如果是,您如何处理?运气好,还是通过一些聪明的解决方法? InvokeAndWait?

其他人已经提到了SwingWorker,您已经了解invoke*方法。 javax.swing.Timer也很有用。安全的多线程编程没有银子弹,虽然好的设计会有很长的路要走。

您可以采取一些措施来检测错误。如果您设计的方法只能从事件调度线程调用,请保护它,以便在有人尝试从另一个线程访问时它会失败:

  public static class ThreadUnsafeAccessException extends RuntimeException {
    private static final long serialVersionUID = 1L;
  }

  @Override public Object getElementAt(int index) {
    if (!SwingUtilities.isEventDispatchThread()) {
      throw new ThreadUnsafeAccessException();
    }
    return decorated.getElementAt(index);
  }

将这种方法改进为在主应用程序线程上进行大量设置的现有应用程序可能意味着一些重大的重构。

答案 5 :(得分:0)

这是一个很好的问题,Software Monkey有正确的答案。如果您想要有效的线程,您必须完全理解Java Memory Model(JMM)。请注意,java内存模型已经使用JSR-133进行了更改,因此您找到的许多旧的线程示例都是错误的。例如,volatile关键字已从几乎无用变为现在几乎与synchronized关键字一样强制执行。

此外,我们现在拥有真正的,无处不在的多核cpu,这意味着真正的多线程。直到几年前的多线程只是伪装在单核cpu上,这就是为什么那么多糟糕的示例代码。它曾经工作过。现在它不会。

说真的,如果约书亚布洛赫错了,那就小心翼翼。这是复杂的东西(是的,他的代码是错误的。让变量变得易变,但它会适用于所有情况)。

哦,是的,Adamski说得对。使用SwingWorker确保在后台线程中完成长时间运行的代码,并且在完成操作数据时,它应该使用done()方法访问Swing。如果您需要阅读,请在制作SwingWorker之前执行并制作信息final

将在EDT上调用的代码示例,很可能是在ActionListener或somesuch(psuedocode)中调用的:

public void someOverlySimpleExampleThatIsCalledOnEDT() {
   /*
    * Any code run here is guaranteed to happen-before 
    * the start of a background thread.
    */
   final String text = myTextField().getText();
   SwingWorker sw = new SwingWorker() {
      private volatile List data;
      public Object doInBackground() {
         //happens in background thread. No Swing access
         data = myDao.getDataFromInternet(text);
         return null;
      }
      public void done() {
         //Happens in EDT. Swing away Merrill
         loadDataIntoJTable(data);
      }
   }
   sw.execute();
   /* 
    * Any code run here happens concurrently with doInBackground(). Be careful. 
    * Usually leave empty
    */
}