【问题标题】:Java: testing thread access to "not thread-safe" methodsJava:测试对“非线程安全”方法的线程访问
【发布时间】:2009-09-04 10:04:14
【问题描述】:

我在 Swing Java 应用程序中处理线程问题的策略是将方法分为三种类型:

  1. 应由 GUI 线程访问的方法。这些方法永远不应该阻塞,并且可能会调用 swing 方法。不是线程安全的。
  2. 应该由非 GUI 线程访问的方法。基本上,这适用于所有(可能)阻塞操作,例如磁盘、数据库和网络访问。他们永远不应该调用 swing 方法。不是线程安全的。
  3. 两者都可以访问的方法。这些方法必须是线程安全的(例如同步的)

我认为这对于通常只有两个线程的 GUI 应用程序来说是一种有效的方法。解决问题确实有助于减少比赛条件的“表面积”。当然需要注意的是,您永远不会不小心从错误的线程调用方法。

我的问题是关于测试的:

是否有测试工具可以帮助我检查是否从正确的线程调用了方法?我知道 SwingUtilities.isEventDispatchThread(),但我真的在寻找使用 Java 注释或面向方面编程的东西,这样我就不必在程序的每个方法中插入相同的样板代码。

【问题讨论】:

  • 创意问题+1
  • 同步不等于线程安全。我强烈建议您阅读 java 5 中的“新”并发库,尤其是我认为 Futures 似乎对 Swing 开发很有用。
  • @Jens:你说得对,我已经稍微编辑了这个问题。

标签: java unit-testing user-interface thread-safety aop


【解决方案1】:

Here 是一个博客条目,其中包含一些检查 EDT 违规的解决方案。一个是自定义重绘管理器,还有一个 AspectJ 解决方案。我过去使用过重绘管理器,发现它非常有用。

【讨论】:

    【解决方案2】:

    感谢所有提示,这是我最终提出的解决方案。这比我想象的要容易。此解决方案同时使用 AspectJ 和 Annotations。它的工作原理是这样的:只需将其中一个注释(定义如下)添加到方法或类,并在开始时插入对 EDT 规则违规的简单检查。尤其是如果你像这样标记整个类,你可以只用少量的额外代码来做大量的测试。

    首先我下载了​​AspectJ并将其添加到我的项目中(在eclipse中你可以使用AJDT

    然后我定义了两个新的注解:

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Target;
    
    /**
     * Indicates that this class or method should only be accessed by threads
     * other than the Event Dispatch Thread
     * <p>
     * Add this annotation to methods that perform potentially blocking operations,
     * such as disk, network or database access. 
     */
    @Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR})
    public @interface WorkerThreadOnly {}
    

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Target;
    
    /**
     * Indicates that this class or method should only be accessed by the 
     * Event Dispatch Thread
     * <p>
     * Add this annotation to methods that call (swing) GUI methods
     */
    @Target({ElementType.METHOD, ElementType.TYPE, ElementType.CONSTRUCTOR})
    public @interface EventDispatchThreadOnly {}
    

    之后,我定义了进行实际检查的方面:

    import javax.swing.SwingUtilities;
    
    /** Check methods / classes marked as WorkerThreadOnly or EventDispatchThreadOnly */
    public aspect ThreadChecking {
    
        /** you can adjust selection to a subset of methods / classes */
        pointcut selection() : execution (* *(..));
    
        pointcut edt() : selection() && 
            (within (@EventDispatchThreadOnly *) ||
            @annotation(EventDispatchThreadOnly));
    
        pointcut worker() : selection() && 
            (within (@WorkerThreadOnly *) ||
            @annotation(WorkerThreadOnly));
    
        before(): edt() {
            assert (SwingUtilities.isEventDispatchThread());
        }
    
        before(): worker() {
            assert (!SwingUtilities.isEventDispatchThread());
        }
    }
    

    现在将@EventDispatchThreadOnly 或@WorkerThreadOnly 添加到应该是线程限制的方法或类中。不要向线程安全方法添加任何内容。

    最后,只需在启用断言的情况下运行(JVM 选项 -ea),您就会很快发现违规的位置(如果有)。

    为了参考,这里是Alexander Potochkin的解决方案,Mark提到的。这是一种类似的方法,但它会检查应用程序中对 swing 方法的调用,而不是应用程序内的调用。这两种方法是互补的,可以一起使用。

    import javax.swing.*;
    
    aspect EdtRuleChecker {
        private boolean isStressChecking = true;
    
        public pointcut anySwingMethods(JComponent c):
             target(c) && call(* *(..));
    
        public pointcut threadSafeMethods():         
             call(* repaint(..)) || 
             call(* revalidate()) ||
             call(* invalidate()) ||
             call(* getListeners(..)) ||
             call(* add*Listener(..)) ||
             call(* remove*Listener(..));
    
        //calls of any JComponent method, including subclasses
        before(JComponent c): anySwingMethods(c) && 
                              !threadSafeMethods() &&
                              !within(EdtRuleChecker) {
         if(!SwingUtilities.isEventDispatchThread() &&
             (isStressChecking || c.isShowing())) 
         {
                 System.err.println(thisJoinPoint.getSourceLocation());
                 System.err.println(thisJoinPoint.getSignature());
                 System.err.println();
          }
        }
    
        //calls of any JComponent constructor, including subclasses
        before(): call(JComponent+.new(..)) {
          if (isStressChecking && !SwingUtilities.isEventDispatchThread()) {
              System.err.println(thisJoinPoint.getSourceLocation());
              System.err.println(thisJoinPoint.getSignature() +
                                    " *constructor*");
              System.err.println();
          }
        }
    }
    

    【讨论】:

      【解决方案3】:

      根据我的阅读,您已经有了具体的解决方案,您只想减少所需的样板代码。

      我会使用拦截技术。

      我们的项目使用 Spring,我们很容易创建一个拦截器,在每次调用之前检查这个条件。在仅测试期间,我们将使用创建拦截器的 Spring 配置(我们可以重用常规 Spring 配置,只需添加即可)。

      要知道我们应该为方法使用什么情况,您可以阅读注解,或者使用其他配置方式。

      【讨论】:

        【解决方案4】:

        到目前为止,最重要的事情是确保 EDT 和非 EDT 之间有明确的区别。在两者之间放置一个清晰的界面。两个阵营都没有方法(SwingWorker,我在看着你)。这通常适用于线程。奇怪的assert java.awt.EventQueue.isDispatchThread(); 在线程之间的接口附近很不错,但不要挂断它。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-09-07
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多