Что такое хороший способ проверить, что метод Java синхронизирован? - PullRequest
16 голосов
/ 05 марта 2010

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

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

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

Ответы [ 4 ]

5 голосов
/ 05 марта 2010

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

Modifier.isSynchronized(method.getModifiers())

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

4 голосов
/ 06 марта 2010

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

  • Вызов 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 голосов
/ 05 марта 2010

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

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

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

1 голос
/ 05 марта 2010

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

...