Каков хороший способ проверить, синхронизирован ли Java-метод?

У меня есть несколько классов, которые реализуют некоторый интерфейс. Интерфейс имеет контракт, что некоторые методы должны быть синхронизированы, а некоторые не должны, и я хочу проверить этот контракт с помощью модульных тестов для всех реализаций. Методы должны использовать синхронизированное ключевое слово или быть заблокированы на this - очень похоже на обертку synchronizedCollection(). Это означает, что я должен иметь возможность наблюдать за ним извне.

Чтобы продолжить пример Collections.synchronizedCollection(), если у меня есть один поток, вызывающий итератор(), я все равно должен быть в состоянии войти такие методы, как add() с другим потоком, потому что итератор() не должен блокировать. С другой стороны, я должен иметь возможность синхронизировать сборку извне и видеть, что другой поток блокируется при добавлении().

Есть ли хороший способ проверить, что метод синхронизирован в тесте JUnit? Я хочу, чтобы избежать длинных заявлений сна.

Ответы

Ответ 1

Большое спасибо 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
        }
    }
}

Ответ 2

Если вы просто хотите проверить, имеет ли метод модификатор synchronized, кроме очевидного (смотря на исходный код /​​Javadoc), вы также можете использовать отражение.

Modifier.isSynchronized(method.getModifiers())

Более общий вопрос тестирования, если метод гарантирует правильную синхронизацию во всех сценариях concurrency, скорее всего, будет неразрешимой проблемой.

Ответ 3

Это все ужасные идеи, но вы могли бы это сделать...

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

Затем дождитесь, когда класс вызовет что-то() в аргументе.

Ответ 4

Используя отражение, вызовите метод Method object и вызовите toString() на нем. "Синхронизированное" ключевое слово должно появиться в выводе toString().