【问题标题】:What is a good way to test that a Java method is synchronized?测试 Java 方法是否同步的好方法是什么?
【发布时间】:2010-03-05 00:20:27
【问题描述】:

我有几个类实现了一些接口。该接口有一个契约,一些方法应该同步,有些不应该同步,我想通过单元测试来验证所有实现的契约。这些方法应该使用 synchronized 关键字或锁定在 this - 非常类似于 synchronizedCollection() 包装器。这意味着我应该能够从外部观察它。

继续Collections.synchronizedCollection() 的示例,如果我有一个线程调用 iterator(),我应该仍然能够使用另一个线程进入 add() 之类的方法,因为 iterator() 不应该进行任何锁定。另一方面,我应该能够在外部同步集合,并看到 add() 上的另一个线程阻塞。

有没有一种好方法可以测试 JUnit 测试中的方法是否同步?我想避免长时间的睡眠声明。

【问题讨论】:

  • 您的意思是要测试他们的声明中是否包含“同步”关键字?还是您的意思是自动测试以查看该类是否存在一些线程问题...?
  • 你有资源吗?您可以在源代码中查看方法是否同步(在正文中找到 synchronized 语句)。顺便说一句,测试一个“良好”线程安全性的类是非常困难的,可能需要一个较低级别的代码检查/分析工具。
  • 检查同步关键字是不够的。同步也可以通过锁来强制执行。

标签: java multithreading unit-testing synchronization thread-safety


【解决方案1】:

如果你只是想检查一个方法是否有synchronized修饰符,除了显而易见的(查看源代码/Javadoc),你还可以使用反射。

Modifier.isSynchronized(method.getModifiers())

测试一个方法是否保证在所有并发场景中正确同步的更一般的问题可能是一个不确定的问题。

【讨论】:

  • 如果它在内部使用synchronized 块怎么办?
  • 嗯,我看到问题描述已被编辑,现在添加了更多新信息......
【解决方案2】:

这些都是可怕的想法,但你可以这样做......

1

    // Substitute this LOCK with your monitor (could be you object you are
    // testing etc.)
    final Object LOCK = new Object();
    Thread locker = new Thread() {
        @Override
        public void run() {
            synchronized (LOCK) {
                try {
                    Thread.sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {
                    System.out.println("Interrupted.");
                    return;
                }
            }
        }
    };

    locker.start();

    Thread attempt = new Thread() {
        @Override
        public void run() {
            // Do your test.
        }
    };

    attempt.start();
    try {
        long longEnough = 3000 * 1000;// It's in nano seconds

        long before = System.nanoTime();
        attempt.join(longEnough);
        long after = System.nanoTime();

        if (after - before < longEnough) {
            throw new AssertionError("FAIL");
        } else {
            System.out.println("PASS");
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        return;
    }
    locker.interrupt();

2

如果你知道参数上的方法总是在任何实现中被调用,你可以传递一个伪装成参数并调用 holdLock() 的模拟对象。

比如:

class Mock implements Argument {
    private final Object LOCK;
    private final Argument real;
    public Mock(Object obj, Argument real){
       this.LOCK=obj;
       this.real = real;
    }

    @Overrides
    public void something(){
        System.out.println("held:"+Thread.holdsLock(LOCK));
        this.real.something();
    }

然后等待类在 Argument 上调用 something()。

【讨论】:

  • 只有在被测试的类使用公共监视器时才有效
  • 这确实回答了我正在寻找的内容,但我担心睡眠语句。
  • @matt b:好吧,既然他说它在“this”上同步,我认为这个要求已经满足了。不过我有一个很长的睡眠声明......
【解决方案3】:

非常感谢 Zwei steinen 编写了我使用的方法。我处理的示例代码中存在一些问题,所以我认为值得在这里发布我的发现。

  • 对 join() 的调用需要毫秒数,而不是纳秒。
  • 这两个线程必须协调,否则尝试线程可以在locker线程抢到锁之前开始和结束。
  • 在我们记录开始时间之前,不应启动尝试线程。否则,该线程会获得足够的领先优势,以至于记录的时间可能会略小于超时,从而导致虚假故障。

这是作为 Scala trait 的同步测试代码:

trait SynchronizedTestTrait
{
    val classUnderTest: AnyRef

    class Gate
    {
        val latch = new java.util.concurrent.CountDownLatch(1)

        def open()
        {
            this.latch.countDown
        }

        def await()
        {
            this.latch.await
        }
    }

    def nanoTime(code: => Unit) =
    {
        val before = System.nanoTime
        code
        val after = System.nanoTime
        after - before
    }

    def assertSynchronized(code: => Unit)
    {
        this.assertThreadSafety(threadSafe = true, millisTimeout = 10L)(code)
    }

    def assertNotSynchronized(code: => Unit)
    {
        this.assertThreadSafety(threadSafe = false, millisTimeout = 60L * 1000L)(code)
    }

    def assertThreadSafety(threadSafe: Boolean, millisTimeout: Long)(code: => Unit)
    {
        def spawn(code: => Unit) =
        {
            val result = new Thread
            {
                override def run = code
            }
            result.start()
            result
        }

        val gate = new Gate

        val lockHolderThread = spawn
        {
            this.classUnderTest.synchronized
            {
                // Don't let the other thread start until we've got the lock
                gate.open()

                // Hold the lock until interruption
                try
                {
                    Thread.sleep(java.lang.Long.MAX_VALUE)
                }
                catch
                {
                    case ignore: InterruptedException => return;
                }
            }
        }

        val measuredNanoTime = nanoTime
        {
            // Don't start until the other thread is synchronized on classUnderTest
            gate.await()
            spawn(code).join(millisTimeout, 0)
        }

        val nanoTimeout = millisTimeout * 1000L * 1000L

        Assert.assertEquals(
            "Measured " + measuredNanoTime + " ns but timeout was " + nanoTimeout + " ns.",
            threadSafe,
            measuredNanoTime > nanoTimeout)

        lockHolderThread.interrupt
        lockHolderThread.join
    }
}

现在假设我们要测试一个简单的类:

class MySynchronized
{
    def synch = this.synchronized{}
    def unsynch = {}
}

测试看起来是这样的:

class MySynchronizedTest extends SynchronizedTestTrait
{
    val classUnderTest = new MySynchronized


    @Test
    def synch_is_synchronized
    {
        this.assertSynchronized
        {
            this.classUnderTest.synch
        }
    }

    @Test
    def unsynch_not_synchronized
    {
        this.assertNotSynchronized
        {
            this.classUnderTest.unsynch
        }
    }
}

【讨论】:

    【解决方案4】:

    使用反射,获取方法的 Method 对象,并在其上调用 toString()。 “synchronized”关键字应该出现在 toString() 的输出中。

    【讨论】:

    • 同步方法本身没有同步关键字。他们还可以使用synchronized(this){...} 块。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多