繁体   English   中英

Java:测试线程访问“非线程安全”方法

[英]Java: testing thread access to “not thread-safe” methods

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

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

我认为这是GUI应用程序的有效方法,通常只有两个线程。 切割问题确实有助于减少竞争条件下的“表面积”。 当然需要注意的是,你从不会意外地从错误的线程中调用方法。

我的问题是关于测试:

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

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

感谢所有提示,这是我最终提出的解决方案。 这比我想象的容易。 此解决方案使用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 {}

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

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),您就会很快发现违规行为(如果有的话)。

出于参考目的,这是Mark提到的Alexander Potochkin的解决方案。 这是一种类似的方法,但它会检查应用中对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();
      }
    }
}

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

我会使用拦截技术。

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

要了解我们应该将哪种情况用于方法,您可以阅读注释,例如,或使用其他配置方法。

到目前为止,最重要的是确保EDT和非EDT之间有明确的分离。 在两者之间建立清晰的界面。 在两个阵营中都没有使用方法的类( SwingWorker ,我在看着你)。 这通常适用于线程。 奇怪的assert java.awt.EventQueue.isDispatchThread(); 线程之间的接口很好,但不要挂在它上面。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM