Java 如果是Swing型号';吸气剂不是';t线程安全,如何处理它们?

Java 如果是Swing型号';吸气剂不是';t线程安全,如何处理它们?,java,swing,thread-safety,Java,Swing,Thread Safety,众所周知,Swing GUI的更新必须仅在EDT中完成。很少有人宣称,从GUI读取内容也必须/应该在EDT中完成。例如,让我们使用方法,它告诉(例如)ToggleButton的状态(“向下”或“向上”) 在我看到的每个示例中,isSelected()都是从主线程或任意线程自由查询的。但是,当我查看DefaultButtonModel的实现时,它不是同步的,并且值也不是易变的。因此,严格地说,isSelected()可能会返回垃圾,如果它是从设置它的线程(当用户按下按钮时是EDT)之外的任何其他线

众所周知,Swing GUI的更新必须仅在EDT中完成。很少有人宣称,从GUI读取内容也必须/应该在EDT中完成。例如,让我们使用方法,它告诉(例如)ToggleButton的状态(“向下”或“向上”)

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

当我被Bloch的高效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和setter来修复,或者通过设置标志
volatile

因此:

  • 在EDT(严格地说)之外查询Swing模型的状态是否错误
  • 如果没有,为什么
  • 如果是,您如何处理?靠运气,还是靠一些巧妙的变通办法?调用EANDWAIT
  • 我从不太担心,但严格地说,是的,可能是错的
  • 不适用
  • 我让GUI中的更改(通过侦听器)更新我自己构造的模型,这些模型不是由Swing包含或构造的。永远不需要询问GUI,因为GUI会更新模型
  • 由于我自己创建了模型(这可以是一些非常简单的东西,例如带有getter/setter和PropertyChangeSupport的字符串),因此如果需要,我可以使访问器同步。由于线程争用之类的原因,我很少遇到不稳定的行为

  • 不,没有错,但与任何跨线程通信一样,如果您决定这样做,您需要确保提供适当的线程安全机制(例如,使用
    同步
    易失性
    )。例如,我通常编写自己的
    TableModel
    实现,通常位于
    List
    上,其中
    X
    是我的业务对象。如果我想让其他线程查询该列表,我将使其成为一个同步的
    集合
    。同样值得注意的是,我通常不会从其他线程更新
    列表
    ;只需查询它
  • 因为这与任何其他多线程应用程序的情况完全相同
  • 我通常使集合同步(请参见1)
  • 警告


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

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

    这里的问题是,除了提供模型的少数例外情况,比如Adamski所提到的,Swing代码不会在任何东西上同步


    值得注意的是,JMM(Java内存模型)在Java5中的JSR-133中发生了非常重要的变化,因此J4和早期JVM的行为可能会产生与J5和更高版本不同的结果。正如您所期望的,修订后的JMM有了很大的改进。

    这是一个很好的问题,软件Monkey给出了正确的答案。如果你想做有效的线程,你必须完全理解(JMM)。请注意,java内存模型在JSR-133中发生了变化,因此您发现的许多旧线程示例都是错误的。例如,volatile关键字已从几乎无用变为现在几乎与synchronized关键字一样强制执行

    此外,我们现在有真正的、无处不在的多核cpu,这意味着真正的多线程。直到几年前,多线程还只是在单核cpu上伪造的,这就是为什么有这么多糟糕的示例代码。它过去是有用的。现在不会了

    说真的,如果约书亚·布洛赫(Joshua Bloch)弄错了,那就小心点。这是一件复杂的事情(是的,他的代码是错误的。让这个变量不稳定,它在所有情况下都能工作)

    哦,是的,亚当斯基说得对。使用
    SwingWorker
    确保长时间运行的代码是在后台线程中完成的,并且在处理完数据后,它应该通过
    done()
    方法访问Swing。如果您需要阅读,请在制作您的
    SwingWorker
    之前阅读,并将信息制作成最终版本

    在EDT上调用的代码示例,最有可能在ActionListener或类似的程序中调用(psuedocode):


    Swing通常不仅是线程安全的,而且是线程敌对的。然而,除了Swing文本之外,大多数模型都是线程无关的。这意味着您可以在任何线程中使用模型,只要您使用标准线程保护。Swing文本是一种线程安全的尝试,但失败了,事实上它是线程敌对的。仅使用事件分派线程(EDT)中的
    文档
    s

    通常的建议是运行潜在的lo
    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
        */
    }
    
      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);
      }