--- /dev/null
+/*
+ * Copyright 2005 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.Assert;
+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 = 500;
+ /**
+ *
+ */
+ private static final int ONE_SECOND = 1000;
+ /**
+ *
+ */
+ private static final int TWO_SECONDS = 2000;
+ 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
+ Thread.sleep(ONE_SECOND);
+ assertTrue(getReaderCount() == 1);
+ t2.start();
+ Thread.sleep(HALF_SECOND);
+ assertTrue(getReaderCount() == 2);
+ t3.start();
+ 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(HALF_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) {
+ Assert.fail("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) {
+ Assert.fail("WriteLocker thread was interrupted: "
+ + Thread.currentThread());
+ }
+
+ _lock.releaseWrite();
+ _lockTest.decrementWriterCount();
+ }
+}