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

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

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

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

4 ответов


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

Modifier.isSynchronized(method.getModifiers())

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


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

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

затем дождитесь, пока класс вызовет something () on Argument.


большое спасибо Zwei steinen за написание подхода, который я использовал. В примере кода, над которым я работал, есть несколько проблем, поэтому я подумал, что стоит опубликовать мои выводы здесь.

  • вызов join () ожидает несколько миллисекунд, а не наносекунд.
  • два потока должны быть скоординированы, иначе поток попытки может начать и закончить все до того, как поток locker захватит блокировку.
  • поток попытки не должен быть началось после того, как мы записали время начала. В противном случае этот поток получает достаточную фору, чтобы записанное время могло быть немного меньше тайм-аута, вызывая ложные сбои.

вот тестовый код синхронизации как черта 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
        }
    }
}

используя отражение, получите объект метода метода и вызовите toString () на нем. Ключевое слово "synchronized" должно появиться в выводе toString ().