代码之家  ›  专栏  ›  技术社区  ›  Joonas Pulakka

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

  •  15
  • Joonas Pulakka  · 技术社区  · 15 年前

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

    在我看到的每一个例子中, isSelected() 从主线程或任何线程进行大量查询。但是,当我查看DefaultButtonModel的实现时,它没有同步,并且值也不是可变的。严格来说, ISSCAN() 如果它是从设置它的线程以外的任何其他线程读取的(当用户按下按钮时是EDT),则返回垃圾。还是我弄错了?

    当布洛赫的有效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 .

    所以:

    1. 在EDT之外查询Swing模型的状态(严格来说)是错误的吗?
    2. 如果没有,怎么会?
    3. 如果是,你怎么处理?靠运气,还是靠一些巧妙的变通办法?InvokeAndWait?
    6 回复  |  直到 15 年前
        1
  •  4
  •   Adamski    15 年前
    1. 不,不是错的,但是与任何跨线程通信一样,如果您决定这样做(例如使用 synchronized volatile )例如,我通常自己写 TableModel 实现,通常位于 List<X> 在哪里? X 是我的业务对象。如果我打算让其他线程查询列表,我将使其同步 Collection . 同样值得注意的是,我通常不会更新 List 来自其他线程;只查询它。
    2. 因为它与其他任何多线程应用程序的情况完全相同。
    3. 我通常使集合同步(参见1)。

    告诫

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

        2
  •  4
  •   L. Cornelius Dol    15 年前

    是的,它是错误的;除非两个线程都同步 在同一个对象上或使用一些其他内存屏障,如JMM定义的volatile 一个或另一个可以观察到不一致的内存内容。时期。故事的结尾。违反这一点可能会对某些甚至许多体系结构产生影响,但最终会让您感到痛苦。

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

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

        3
  •  2
  •   Robert Harvey    15 年前

    一般来说,秋千不仅不安全,而且对线有敌意。然而,除了Swing文本之外,大多数模型都是线程无关的。这意味着,只要使用标准线程保护,就可以在任何线程中使用模型。Swing文本是一种线程安全的尝试,但失败了,实际上是线程敌对的。只使用 Document 来自事件调度线程(EDT)的。

    通常的建议是在EDT之外运行可能长时间运行(通常是阻塞)的任务。然而,线程是困难的。 SwingWorker 是诱发不良设计的一种流行方法-避免它。尝试将EDT和非EDT代码分开。尽量不要共享可变状态。

    线程错误是困难的。您可能在您的机器上看不到它们,但客户可能会看到。可能是由于JRE的后期版本中的优化导致了错误的出现。线程错误很难跟踪。因此,要保守。即使短暂地阻止EDT也比讨厌的错误要好。

        4
  •  1
  •   Carl Smotricz    15 年前
    1. 我从不担心,但严格地说,是的,这可能是错误的。
    2. N/A。
    3. 我让GUI中的变更(通过监听器)更新我自己的结构模型,这些模型不是由Swing包含或构造的。因为图形用户界面更新了模型,所以不需要询问图形用户界面。

    因为我自己创建了模型(这可以是非常简单的事情,比如一个带有getter/setter和propertychangesupport的字符串),所以如果需要,我可以使访问器同步。由于线程争用之类的原因,我很少遇到不稳定的行为。

        5
  •  1
  •   McDowell rahul gupta    15 年前

    如果是,你怎么处理?靠运气,还是靠一些巧妙的变通办法?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);
      }
    

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

        6
  •  0
  •   Nemi    15 年前

    这是一个很好的问题,软件猴子有正确的答案。如果要执行有效的线程,必须完全理解 Java Memory Model (JMM)。注意,Java内存模型 已经改变 有了JSR-133,很多旧的线程示例都是错误的。例如,volatile关键字已经从几乎无用变为现在几乎和synchronized关键字一样强制。

    此外,我们现在拥有真正的、无所不在的多核CPU,这意味着真正的多线程。直到几年前,多线程只是在单核CPU上伪造,这就是为什么有这么多坏的示例代码存在的原因。它曾经起作用。现在不会了。

    说真的,如果乔舒亚·布洛克能弄错的话,就要小心行事。这是复杂的事情(是的,他的代码是错误的。使这个变量易变,它在所有情况下都能工作。

    哦,是的,阿达姆斯基说得对。使用A SwingWorker 为了确保长时间运行的代码是在后台线程中完成的,并且在完成对数据的操作后,它应该访问 done() 方法。如果你需要阅读,就去读吧 之前 制作你的 摇摆工人 并提供信息 final .

    在EDT上调用的代码示例,最有可能是在ActionListener或其他(psuedCode)中:

    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
        */
    }