简体   繁体   English

有什么好的方法可以测试Java方法是否已同步?

[英]What is a good way to test that a Java method is synchronized?

I have several classes that implement some interface. 我有几个实现某些接口的类。 The interface has a contract, that some methods should be synchronized, and some should not, and I want to verify that contract through unit tests for all the implementations. 该接口有一个约定,某些方法应该同步,有些方法应该不同步,我想通过单元测试针对所有实现来验证该约定。 The methods should use the synchronized keyword or be locked on this - very similar to the synchronizedCollection() wrapper. 该方法应该使用synchronized关键字或锁定this -非常类似synchronizedCollection()包装。 That means I should be able to observe it externally. 这意味着我应该能够从外部观察它。

To continue the example of Collections.synchronizedCollection() if I have one thread calling iterator(), I should still be able to get into methods like add() with another thread because iterator() should not do any locking. 如果我有一个调用iterator()的线程,以继续Collections.synchronizedCollection()的示例,我仍然应该能够通过另一个线程进入类似add()的方法,因为iterator()不应进行任何锁定。 On the other hand, I should be able to synchronize on the collection externally and see that another thread blocks on add(). 另一方面,我应该能够在集合上进行外部同步,并看到另一个线程在add()上阻塞。

Is there a good way to test that a method is synchronized in a JUnit test? 有没有很好的方法来测试方法是否在JUnit测试中同步? I want to avoid long sleep statements. 我想避免长时间睡眠的陈述。

If you just want to check if a method has the synchronized modifier, aside from the obvious (looking at the source code/Javadoc), you can also use reflection. 如果您只想检查一个方法是否具有synchronized修饰符,除了显而易见的(查看源代码/ Javadoc),还可以使用反射。

Modifier.isSynchronized(method.getModifiers())

The more general question of testing if a method guarantees proper synchronization in all concurrency scenarios is likely to be an undecidable problem. 测试一个方法是否能在所有并发方案中确保正确同步的更普遍的问题可能是无法确定的问题。

These are all horrible ideas, but you could do this... 这些都是可怕的想法,但是你可以做到这一点。

1 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 2

If you know that methods on the arguments are always invoked in any implementation, you can pass a mock object that disguises as the argument and calls holdsLock(). 如果您知道在任何实现中始终会调用参数上的方法,则可以传递伪装为参数的模拟对象并调用holdLock()。

So like: 像这样:

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

Then wait for the class to invoke something() on Argument. 然后等待该类在Argument上调用something()。

A big thank you to Zwei steinen for writing up the approach I used. 非常感谢Zwei steinen编写了我使用的方法。 There are a few problems in the example code that I worked through, so I thought it would be worth posting my findings here. 我处理的示例代码中存在一些问题,因此我认为值得在这里发表我的发现。

  • The call to join() expects a number of milliseconds, not nanoseconds. 对join()的调用期望以毫秒为单位,而不是纳秒。
  • The two threads must be coordinated, otherwise the attempt thread can start and finish all before the locker thread grabs the lock. 这两个线程必须协调,否则尝试线程可以在更衣室线程获取锁之前开始和结束所有线程。
  • The attempt thread should not be started until after we record the start time. 在记录开始时间之前,不应启动尝试线程。 Otherwise that thread gets enough of a head start that the recorded time can be slightly less than the timeout, causing spurious failures. 否则,该线程将获得足够的领先优势,以至于记录的时间可能比超时时间稍短,从而导致虚假故障。

Here is the synchronization test code as a Scala trait: 这是作为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
    }
}

Now let's say we want to test a simple class: 现在,我们要测试一个简单的类:

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

The test looks this: 测试看起来是这样的:

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

Using reflection, get the method's Method object, and invoke toString() on it. 使用反射,获取方法的Method对象,并在其上调用toString()。 The "synchronized" keyword should appear in toString()'s output. “ synchronized”关键字应出现在toString()的输出中。

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

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