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

发布于 2024-08-23 20:45:52 字数 545 浏览 5 评论 0原文

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

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

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

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. 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. On the other hand, I should be able to synchronize on the collection externally and see that another thread blocks on add().

Is there a good way to test that a method is synchronized in a JUnit test? I want to avoid long sleep statements.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

半城柳色半声笛 2024-08-30 20:45:52

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

Modifier.isSynchronized(method.getModifiers())

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

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.

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.

能否归途做我良人 2024-08-30 20:45:52

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

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

These are all horrible ideas, but you could do this...

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

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

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.

天气好吗我好吗 2024-08-30 20:45:52

非常感谢 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
        }
    }
}

A big thank you to Zwei steinen for writing up the approach I used. 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.
  • 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:

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
        }
    }
}
爱冒险 2024-08-30 20:45:52

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

Using reflection, get the method's Method object, and invoke toString() on it. The "synchronized" keyword should appear in toString()'s output.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文