代码之家  ›  专栏  ›  技术社区  ›  Craig P. Motlin

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

  •  16
  • Craig P. Motlin  · 技术社区  · 15 年前

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

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

    有没有一种好的方法来测试方法在JUnit测试中是同步的?我想避免长时间的睡眠。

    4 回复  |  直到 15 年前
        1
  •  5
  •   polygenelubricants    15 年前

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

    Modifier.isSynchronized(method.getModifiers())
    

    如果一个方法在所有并发场景中都能保证正确的同步,那么测试这个更一般的问题可能是一个无法确定的问题。

        2
  •  4
  •   Enno Shioji    15 年前

    这些都是可怕的想法,但你可以这么做…

        // 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();
    

    如果知道参数上的方法总是在任何实现中调用,则可以传递伪装为参数的模拟对象并调用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();
        }
    

    然后等待类对参数调用something()。

        3
  •  4
  •   Craig P. Motlin    15 年前

    非常感谢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
            }
        }
    }
    
        4
  •  1
  •   Mike W    15 年前

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