测试Java方法是否同步的好方法是什么?

时间:2010-03-05 00:20:28

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

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

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

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

4 个答案:

答案 0 :(得分:5)

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

Modifier.isSynchronized(method.getModifiers())

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

答案 1 :(得分:4)

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

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

如果您知道在任何实现中始终调用参数上的方法,则可以传递伪装成参数的模拟对象并调用holdsLock()。

所以喜欢:

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()。

答案 2 :(得分:4)

非常感谢Zwei steinen撰写我使用过的方法。我完成的示例代码中存在一些问题,因此我认为值得在此处发布我的发现。

  • 对join()的调用需要几毫秒,而不是纳秒。
  • 必须协调两个线程,否则尝试线程可以在锁定线程抓取锁之前开始和完成所有线程。
  • 在记录开始时间之前,不应启动尝试线程。否则该线程会有足够的启动时间,记录的时间可能会略微超过超时,从而导致虚假失败。

以下是Scala特征的同步测试代码:

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

答案 3 :(得分:1)

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