/* * Copyright 2005-2010 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.wamblee.concurrency; import junit.framework.TestCase; /** * Testing the read-write lock class. Note: in case of problems, test cases * could hang. * * @see ReadWriteLock */ public class ReadWriteLockTest extends TestCase { private static final int HALF_SECOND = 100; private static final int ONE_SECOND = 200; private static final int TWO_SECONDS = 400; private ReadWriteLock lock; private int nReaders; private int nWriters; /** * Constructor for ReadWriteLockTest. * * @param aName */ public ReadWriteLockTest(String aName) { super(aName); } private synchronized int getReaderCount() { return nReaders; } private synchronized int getWriterCount() { return nWriters; } synchronized void incrementReaderCount() { nReaders++; } synchronized void incrementWriterCount() { nWriters++; } synchronized void decrementReaderCount() { nReaders--; } synchronized void decrementWriterCount() { nWriters--; } /* * @see TestCase#setUp() */ protected void setUp() throws Exception { super.setUp(); lock = new ReadWriteLock(); } /* * @see TestCase#tearDown() */ protected void tearDown() throws Exception { lock = null; super.tearDown(); } /** * Acquire and release a read lock. */ public void testRead() { lock.acquireRead(); lock.releaseRead(); } /** * Acquire and release a write lock. */ public void testWrite() { lock.acquireWrite(); lock.releaseWrite(); } /** * Verify concurrent access by multiple readers is possible. * * @throws InterruptedException * May not occur. */ public void testMultipleReaders() throws InterruptedException { Runnable runnable = new ReadLocker(lock, this, TWO_SECONDS); Thread t1 = new Thread(runnable); t1.start(); Thread t2 = new Thread(runnable); t2.start(); Thread.sleep(ONE_SECOND); assertTrue("Not enough readers!", getReaderCount() == 2); t1.join(); t2.join(); } /** * Verify that only one writer at a time can acquire the write lock. * * @throws InterruptedException * May not occur. */ public void testSingleWriter() throws InterruptedException { WriteLocker writer = new WriteLocker(lock, this, ONE_SECOND); Thread t1 = new Thread(writer); Thread t2 = new Thread(writer); t1.start(); t2.start(); Thread.sleep(HALF_SECOND); assertTrue("Wrong writer count: " + getWriterCount(), getWriterCount() == 1); t1.join(); t2.join(); } /** * Verify that multiple writers cannot acquire the write lock concurrently. * * @throws InterruptedException * May not occur. */ public void testMultipleWriters() throws InterruptedException { WriteLocker writer1 = new WriteLocker(lock, this, HALF_SECOND + ONE_SECOND); WriteLocker writer2 = new WriteLocker(lock, this, ONE_SECOND); Thread t1 = new Thread(writer1); Thread t2 = new Thread(writer2); t1.start(); Thread.sleep(HALF_SECOND); assertTrue(getWriterCount() == 1); t2.start(); Thread.sleep(HALF_SECOND); assertTrue(getWriterCount() == 1); // first writer still // has the lock. Thread.sleep(ONE_SECOND); // at t = 2, the second writer still must have // a lock. assertTrue(getWriterCount() == 1); t1.join(); t2.join(); } /** * Verify that after the first reader acquires a lock, a subsequent writer * can only acquire the lock after the reader has released it. * * @throws InterruptedException * May not occur. */ public void testReadWrite1() throws InterruptedException { ReadLocker readLocker = new ReadLocker(lock, this, TWO_SECONDS); Thread t1 = new Thread(readLocker); WriteLocker writeLocker = new WriteLocker(lock, this, TWO_SECONDS); Thread t2 = new Thread(writeLocker); t1.start(); // acquire read lock Thread.sleep(HALF_SECOND); assertTrue(getReaderCount() == 1); t2.start(); Thread.sleep(HALF_SECOND); // 1 second underway, reader still holding the // lock so write lock cannot be acquired. assertTrue(getReaderCount() == 1); assertTrue(getWriterCount() == 0); Thread.sleep(ONE_SECOND + HALF_SECOND); // 2.5 seconds underway, read lock released and // write lock must be acquired. assertTrue("Wrong no. of readers: " + getReaderCount(), getReaderCount() == 0); assertTrue(getWriterCount() == 1); t1.join(); t2.join(); } /** * Verify that when multiple readers have acquired a read lock, the writer * can only acquire the lock after all readers have released it. * * @throws InterruptedException * May not occur. */ public void testReadWrite2() throws InterruptedException { ReadLocker readLocker1 = new ReadLocker(lock, this, TWO_SECONDS + HALF_SECOND); ReadLocker readLocker2 = new ReadLocker(lock, this, TWO_SECONDS + HALF_SECOND); Thread t1 = new Thread(readLocker1); Thread t2 = new Thread(readLocker2); WriteLocker writeLocker = new WriteLocker(lock, this, TWO_SECONDS); Thread t3 = new Thread(writeLocker); t1.start(); // acquire read lock [0, 2.5] Thread.sleep(ONE_SECOND); // t = 1 assertTrue(getReaderCount() == 1); t2.start(); // acquire read lock [1, 3.5] Thread.sleep(HALF_SECOND); // t = 1.5 assertTrue(getReaderCount() == 2); t3.start(); // write lock Thread.sleep(HALF_SECOND); // 2 seconds, assertTrue(getReaderCount() == 2); assertTrue(getWriterCount() == 0); Thread.sleep(ONE_SECOND); // 3 seconds underway, first read lock must // have been released. assertTrue(getReaderCount() == 1); assertTrue(getWriterCount() == 0); Thread.sleep(ONE_SECOND); // 4 seconds underway, write lock must have // been acquired. assertTrue(getReaderCount() == 0); assertTrue(getWriterCount() == 1); t1.join(); t2.join(); t3.join(); } /** * Verify that after a writer acquires a lock, a subsequent reader can only * acquire the lock after the writer has released it. * * @throws InterruptedException * May not occur. */ public void testReadWrite3() throws InterruptedException { ReadLocker readLocker = new ReadLocker(lock, this, TWO_SECONDS); Thread t1 = new Thread(readLocker); WriteLocker writeLocker = new WriteLocker(lock, this, TWO_SECONDS); Thread t2 = new Thread(writeLocker); t2.start(); // acquire write lock Thread.sleep(HALF_SECOND); assertTrue(getWriterCount() == 1); t1.start(); Thread.sleep(HALF_SECOND); // 1 second underway, writer still holding the // lock so read lock cannot be acquired. assertTrue(getWriterCount() == 1); assertTrue(getReaderCount() == 0); Thread.sleep(ONE_SECOND + HALF_SECOND); // 2.5 seconds underway, write lock released and // read lock must be acquired. assertTrue("Wrong no. of writers: " + getReaderCount(), getWriterCount() == 0); assertTrue(getReaderCount() == 1); t1.join(); t2.join(); } /* * The following test cases are for testing whether or not the read write * lock checks the locking correctly. Strictly speaking, these checks * wouldn't be necessary because it involves the contract of the * ReadWriteLock which must be obeyed by users of the ReadWriteLock. * Nevertheless, this is tested anyway to be absolutely sure. */ /** * Acquire a read lock from one thread, release it from another. Verify that * a RuntimeException is thrown. * * @throws InterruptedException * May not occur. */ public void testReleaseReadFromWrongThread() throws InterruptedException { Thread t1 = null; try { t1 = new Thread(new Runnable() { public void run() { ReadWriteLockTest.this.lock.acquireRead(); } }); t1.start(); Thread.sleep(ONE_SECOND); // wait until thread is started lock.releaseRead(); // release lock from wrong thread. } catch (RuntimeException e) { return; // ok } finally { t1.join(); } fail(); } /** * Acquire a write lock from one thread, release it from another. Verify * that a RuntimeException is thrown. * * @throws InterruptedException * May not occur. */ public void testReleaseWriteFromWrongThread() throws InterruptedException { Thread t1 = null; try { t1 = new Thread(new Runnable() { public void run() { ReadWriteLockTest.this.lock.acquireWrite(); } }); t1.start(); Thread.sleep(ONE_SECOND); // wait until thread is started lock.releaseWrite(); // release lock from wrong thread. } catch (RuntimeException e) { return; // ok } finally { t1.join(); } fail(); } /** * Try to acquire a read lock multiple times. Verify that a RuntimeException * is thrown. */ public void testAcquireReadTwice() { try { lock.acquireRead(); lock.acquireRead(); } catch (RuntimeException e) { // ok return; } fail(); } /** * Try to acquire a write lock multiple times. Verify that a * RuntimeException is thrown. */ public void testAcquireWriteTwice() { try { lock.acquireWrite(); lock.acquireWrite(); } catch (RuntimeException e) { return; // ok } fail(); } /** * Acquire the lock for reading and directly afterwards acquire it for * writing. Verify that a RuntimeException is thrown. */ public void testAcquireReadFollowedByWrite() { try { lock.acquireRead(); lock.acquireWrite(); } catch (RuntimeException e) { return; // ok } fail(); } /** * Acquire the lock for writing and directly afterwards acquire it for * reading. Verify that a RuntimeException is thrown. */ public void testAcquireWriteFollowedByRead() { try { lock.acquireWrite(); lock.acquireRead(); } catch (RuntimeException e) { return; // ok } fail(); } /** * Acquire a read lock and release it as a write lock. Verify that a * RuntimeException is thrown. */ public void testAcquireReadFollowedByReleaseaWrite() { try { lock.acquireRead(); lock.releaseWrite(); } catch (RuntimeException e) { return; // ok } fail(); } /** * Acquire a write lock and release it as a read lock. Verify that a * RuntimeException is thrown. */ public void testAcquireWriteFollowedByReleaseRead() { try { lock.acquireWrite(); lock.releaseRead(); } catch (RuntimeException e) { return; // ok } fail(); } } /** * ReadLocker acquires a read lock and performs a callback when the lock as been * acquired, sleeps for a designated amount of time, releases the read lock, and * performs a callback after the lock has been released. */ class ReadLocker implements Runnable { private ReadWriteLock lock; private ReadWriteLockTest lockTest; private int sleepTime; public ReadLocker(ReadWriteLock aLock, ReadWriteLockTest aLockTest, int aSleepTime) { lock = aLock; lockTest = aLockTest; sleepTime = aSleepTime; } public void run() { lock.acquireRead(); lockTest.incrementReaderCount(); try { Thread.sleep(sleepTime); } catch (InterruptedException e) { throw new RuntimeException("ReadLocker thread was interrupted." + Thread.currentThread()); } lock.releaseRead(); lockTest.decrementReaderCount(); } } /** * WriteLocker acquires a write lock and performs a callback when the lock as * been acquired, sleeps for a designated amount of time, releases the write * lock, and performs a callback after the lock has been released. */ class WriteLocker implements Runnable { private ReadWriteLock lock; private ReadWriteLockTest lockTest; private int sleepTime; public WriteLocker(ReadWriteLock aLock, ReadWriteLockTest aLockTest, int aSleepTime) { lock = aLock; lockTest = aLockTest; sleepTime = aSleepTime; } public void run() { lock.acquireWrite(); lockTest.incrementWriterCount(); try { Thread.sleep(sleepTime); } catch (InterruptedException e) { throw new RuntimeException("WriteLocker thread was interrupted: " + Thread.currentThread()); } lock.releaseWrite(); lockTest.decrementWriterCount(); } }