From 77d45c35e0ae886c01493e162adf6023dbcb4155 Mon Sep 17 00:00:00 2001 From: hoswey Date: Sun, 10 Jan 2016 21:23:52 +0800 Subject: [PATCH] implements the #75 reader writer lock, fix the problem of review --- reader-writer-lock/etc/reader-writer-lock.png | Bin 37534 -> 39623 bytes .../etc/reader-writer-lock.ucls | 80 ++++--- reader-writer-lock/index.md | 4 +- reader-writer-lock/pom.xml | 38 ++-- .../com/iluwatar/reader/writer/lock/App.java | 83 +++----- .../com/iluwatar/reader/writer/lock/Lock.java | 18 -- .../iluwatar/reader/writer/lock/Reader.java | 40 ++++ .../reader/writer/lock/ReaderWriterLock.java | 200 ++++++++++++------ .../iluwatar/reader/writer/lock/Writer.java | 40 ++++ .../iluwatar/reader/writer/lock/AppTest.java | 4 - .../writer/lock/ReaderAndWriterTest.java | 84 ++++++++ .../reader/writer/lock/ReaderTest.java | 45 ++++ .../reader/writer/lock/WriterTest.java | 52 +++++ 13 files changed, 482 insertions(+), 206 deletions(-) delete mode 100644 reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Lock.java create mode 100644 reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Reader.java create mode 100644 reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Writer.java create mode 100644 reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderAndWriterTest.java create mode 100644 reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderTest.java create mode 100644 reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/WriterTest.java diff --git a/reader-writer-lock/etc/reader-writer-lock.png b/reader-writer-lock/etc/reader-writer-lock.png index 06207b502bf9387126a63a4f84cde5485903fd7b..f7b6005309263d2dc8baf7505fd68091a580011b 100644 GIT binary patch literal 39623 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 literal 37534 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 - + + + + + + + - + - - + - - + - - - - - - - - - + @@ -54,36 +54,28 @@ - - + + - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - - - - - + + + diff --git a/reader-writer-lock/index.md b/reader-writer-lock/index.md index 75f57a4cd..91d16892c 100644 --- a/reader-writer-lock/index.md +++ b/reader-writer-lock/index.md @@ -1,7 +1,7 @@ --- layout: pattern -title: Producer Consumer -folder: reader writer lock +title: Reader Writer Lock +folder: reader-writer-lock permalink: /patterns/reader-writer-lock/ categories: Concurrent tags: diff --git a/reader-writer-lock/pom.xml b/reader-writer-lock/pom.xml index f1553a4d1..14b17011d 100644 --- a/reader-writer-lock/pom.xml +++ b/reader-writer-lock/pom.xml @@ -1,18 +1,24 @@ - - 4.0.0 - - com.iluwatar - java-design-patterns - 1.9.0-SNAPSHOT - - reader-writer-lock - - - junit - junit - test - - + + 4.0.0 + + com.iluwatar + java-design-patterns + 1.10.0-SNAPSHOT + + reader-writer-lock + + + junit + junit + test + + + org.mockito + mockito-core + test + + + diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/App.java b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/App.java index aa36ef47c..1d02b6cad 100644 --- a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/App.java +++ b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/App.java @@ -1,24 +1,31 @@ package com.iluwatar.reader.writer.lock; -import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; /** - * Reader writer lock is a synchronization primitive that solves one of the readers–writers - * problems. An RW lock allows concurrent access for read-only operations, while write operations - * require exclusive access. + * + * In a multiple thread applications, the threads may try to synchronize the shared resources + * regardless of read or write operation. It leads to a low performance especially in a "read more + * write less" system as indeed the read operations are thread-safe to another read operation. *

- * Below example use two mutexes to demonstrate the concurrent access of mutilple readers and + * Reader writer lock is a synchronization primitive that try to resolve this problem. This pattern + * allows concurrent access for read-only operations, while write operations require exclusive + * access. This means that multiple threads can read the data in parallel but an exclusive lock is + * needed for writing or modifying data. When a writer is writing the data, all other writers or + * readers will be blocked until the writer is finished writing. + * + *

+ * This example use two mutex to demonstrate the concurrent access of multiple readers and * writers. * + * + * @author hongshuwei@gmail.com */ public class App { - private static Random ran = new Random(); - /** * Program entry point * @@ -26,57 +33,25 @@ public class App { */ public static void main(String[] args) { - ExecutorService es = Executors.newFixedThreadPool(1000); + ExecutorService executeService = Executors.newFixedThreadPool(1000); ReaderWriterLock lock = new ReaderWriterLock(); - AtomicInteger index = new AtomicInteger(0); - IntStream.range(0, 100).forEach(i -> { - Runnable task = null; - if (ran.nextFloat() <= 0.6) { - task = new Runnable() { - @Override - public void run() { - Lock writeLock = lock.writeLock(); - writeLock.lock(); - try { - int cur = index.getAndIncrement(); - System.out.println("Writer " + cur + " begin"); - simulateReadOrWrite(); - System.out.println("Writer " + cur + " finish"); - } finally { - writeLock.unlock(); - } - } - }; - } else { - task = new Runnable() { + // Start 10 readers + IntStream.range(0, 10) + .forEach(i -> executeService.submit(new Reader("Reader " + i, lock.readLock()))); - @Override - public void run() { - Lock readLock = lock.readLock(); - readLock.lock(); - try { - int cur = index.getAndIncrement(); - System.out.println("Reader " + cur + " begin"); - simulateReadOrWrite(); - System.out.println("Reader " + cur + " finish"); - - } finally { - readLock.unlock(); - } - } - }; - } - es.submit(task); - }); - - } - - private static void simulateReadOrWrite() { + // Start 10 writers + IntStream.range(0, 10) + .forEach(i -> executeService.submit(new Writer("Writer " + i, lock.writeLock()))); + // In the system console, it can see that the read operations are executed concurrently while + // write operations are exclusive. + executeService.shutdown(); try { - Thread.sleep((long) (ran.nextFloat() * 10)); + executeService.awaitTermination(5, TimeUnit.SECONDS); } catch (InterruptedException e) { - e.printStackTrace(); + System.out.println("Error waiting for ExecutorService shutdown"); } + } + } diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Lock.java b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Lock.java deleted file mode 100644 index 7cbe47749..000000000 --- a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Lock.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.iluwatar.reader.writer.lock; - -/** - * Lock interface - */ -public interface Lock { - - /** - * Try to lock, it will wait until get the lock - */ - public void lock(); - - /** - * Release lock - */ - public void unlock(); -} - diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Reader.java b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Reader.java new file mode 100644 index 000000000..5704fdf28 --- /dev/null +++ b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Reader.java @@ -0,0 +1,40 @@ +package com.iluwatar.reader.writer.lock; + +import java.util.concurrent.locks.Lock; + +/** + * Reader class, read when it acquired the read lock + */ +public class Reader implements Runnable { + + private Lock readLock; + + private String name; + + public Reader(String name, Lock readLock) { + this.name = name; + this.readLock = readLock; + } + + @Override + public void run() { + readLock.lock(); + try { + read(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + readLock.unlock(); + } + } + + /** + * Simulate the read operation + * + */ + public void read() throws InterruptedException { + System.out.println(name + " begin"); + Thread.sleep(100); + System.out.println(name + " finish"); + } +} diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java index 2d92696e3..b7edd149c 100644 --- a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java +++ b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java @@ -2,62 +2,103 @@ package com.iluwatar.reader.writer.lock; import java.util.HashSet; import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReadWriteLock; /** * Class responsible for control the access for reader or writer + * + * Allows multiple readers to hold the lock at same time, but if any writer holds the lock then + * readers wait. If reader holds the lock then writer waits. This lock is not fair. */ -public class ReaderWriterLock { +public class ReaderWriterLock implements ReadWriteLock { + + + private Object readerMutex = new Object(); + + private int currentReaderCount = 0; /** - * Mutex for reader + * Global mutex is used to indicate that whether reader or writer gets the lock in the moment. + *

+ * 1. When it contains the reference of {@link readerLock}, it means that the lock is acquired by + * the reader, another reader can also do the read operation concurrently.
+ * 2. When it contains the reference of reference of {@link writerLock}, it means that the lock is + * acquired by the writer exclusively, no more reader or writer can get the lock. + *

+ * This is the most important field in this class to control the access for reader/writer. */ - private Object r = new Object(); - - /** - * Global mutex for reader or writer, use to save the holding object - */ - private Set g = new HashSet<>(); - - /** - * Current reader count - */ - private int readerCount = 0; - - private ReaderLock readLock = new ReaderLock(); - private WriterLock writeLock = new WriterLock(); + private Set globalMutex = new HashSet<>(); + private ReadLock readerLock = new ReadLock(); + private WriteLock writerLock = new WriteLock(); + @Override public Lock readLock() { - return readLock; + return readerLock; } - + @Override public Lock writeLock() { - return writeLock; + return writerLock; } + /** + * return true when globalMutex hold the reference of writerLock + */ + private boolean doesWriterOwnThisLock() { + return globalMutex.contains(writerLock); + } + + /** + * return true when globalMutex hold the reference of readerLock + */ + private boolean doesReaderOwnThisLock() { + return globalMutex.contains(readerLock); + } + + /** + * Nobody get the lock when globalMutex contains nothing + * + */ + private boolean isLockFree() { + return globalMutex.isEmpty(); + } + + private void waitUninterruptibly(Object o) { + try { + o.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } /** * Reader Lock, can be access for more than one reader concurrently if no writer get the lock */ - private class ReaderLock implements Lock { + private class ReadLock implements Lock { @Override public void lock() { - synchronized (r) { - - readerCount++; - if (readerCount == 1) { - - synchronized (g) { + synchronized (readerMutex) { + currentReaderCount++; + if (currentReaderCount == 1) { + // Try to get the globalMutex lock for the first reader + synchronized (globalMutex) { while (true) { - if (isLockFree() || isReaderOwnThisLock()) { - g.add(this); + // If the no one get the lock or the lock is locked by reader, just set the reference + // to the globalMutex to indicate that the lock is locked by Reader. + if (isLockFree() || doesReaderOwnThisLock()) { + globalMutex.add(this); break; } else { - waitUninterruptely(g); + // If lock is acquired by the write, let the thread wait until the writer release + // the lock + waitUninterruptibly(globalMutex); } } } @@ -66,82 +107,105 @@ public class ReaderWriterLock { } } - @Override public void unlock() { - synchronized (r) { - readerCount--; - if (readerCount == 0) { - synchronized (g) { - g.remove(this); - g.notifyAll(); + synchronized (readerMutex) { + currentReaderCount--; + // Release the lock only when it is the last reader, it is ensure that the lock is released + // when all reader is completely. + if (currentReaderCount == 0) { + synchronized (globalMutex) { + // Notify the waiter, mostly the writer + globalMutex.remove(this); + globalMutex.notifyAll(); } } } } - } + @Override + public void lockInterruptibly() throws InterruptedException { + throw new UnsupportedOperationException(); + } + @Override + public boolean tryLock() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public Condition newCondition() { + throw new UnsupportedOperationException(); + } + + } /** * Writer Lock, can only be accessed by one writer concurrently */ - private class WriterLock implements Lock { + private class WriteLock implements Lock { @Override public void lock() { - synchronized (g) { + synchronized (globalMutex) { while (true) { - + // When there is no one acquired the lock, just put the writeLock reference to the + // globalMutex to indicate that the lock is acquired by one writer. + // It is ensure that writer can only get the lock when no reader/writer acquired the lock. if (isLockFree()) { - g.add(this); + globalMutex.add(this); break; - } else if (isWriterOwnThisLock()) { - waitUninterruptely(g); - } else if (isReaderOwnThisLock()) { - waitUninterruptely(g); + } else if (doesWriterOwnThisLock()) { + // Wait when other writer get the lock + waitUninterruptibly(globalMutex); + } else if (doesReaderOwnThisLock()) { + // Wait when other reader get the lock + waitUninterruptibly(globalMutex); } else { - throw new RuntimeException("it should never reach here"); + throw new AssertionError("it should never reach here"); } } } } - @Override public void unlock() { - synchronized (g) { - g.remove(this); - g.notifyAll(); + synchronized (globalMutex) { + globalMutex.remove(this); + // Notify the waiter, other writer or reader + globalMutex.notifyAll(); } } - } - private boolean isWriterOwnThisLock() { - return g.contains(writeLock); - } + @Override + public void lockInterruptibly() throws InterruptedException { + throw new UnsupportedOperationException(); + } - private boolean isReaderOwnThisLock() { - return g.contains(readLock); - } + @Override + public boolean tryLock() { + throw new UnsupportedOperationException(); + } - private boolean isLockFree() { - return g.isEmpty(); - } + @Override + public boolean tryLock(long time, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } - - - private void waitUninterruptely(Object o) { - try { - o.wait(); - } catch (InterruptedException e) { - e.printStackTrace(); + @Override + public Condition newCondition() { + throw new UnsupportedOperationException(); } } -} +} diff --git a/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Writer.java b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Writer.java new file mode 100644 index 000000000..d7afa385a --- /dev/null +++ b/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Writer.java @@ -0,0 +1,40 @@ +package com.iluwatar.reader.writer.lock; + +import java.util.concurrent.locks.Lock; + +/** + * Writer class, write when it acquired the write lock + */ +public class Writer implements Runnable { + + private Lock writeLock = null; + + private String name; + + public Writer(String name, Lock writeLock) { + this.name = name; + this.writeLock = writeLock; + } + + + @Override + public void run() { + writeLock.lock(); + try { + write(); + } catch (InterruptedException e) { + e.printStackTrace(); + } finally { + writeLock.unlock(); + } + } + + /** + * Simulate the write operation + */ + public void write() throws InterruptedException { + System.out.println(name + " begin"); + Thread.sleep(100); + System.out.println(name + " finish"); + } +} diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/AppTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/AppTest.java index 95656b14e..5dd6feaab 100644 --- a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/AppTest.java +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/AppTest.java @@ -2,12 +2,8 @@ package com.iluwatar.reader.writer.lock; import org.junit.Test; -import com.iluwatar.reader.writer.lock.App; - /** - * * Application test - * */ public class AppTest { diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderAndWriterTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderAndWriterTest.java new file mode 100644 index 000000000..b9516f3a7 --- /dev/null +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderAndWriterTest.java @@ -0,0 +1,84 @@ +package com.iluwatar.reader.writer.lock; + +import static org.mockito.Mockito.after; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * @author hongshuwei@gmail.com + */ +public class ReaderAndWriterTest { + + ExecutorService executeService; + + @Before + public void setup() { + executeService = Executors.newFixedThreadPool(2); + } + + /** + * Verify reader and writer can only get the lock to read and write orderly + */ + @Test + public void testReadAndWrite() throws Exception { + + ReaderWriterLock lock = new ReaderWriterLock(); + + Reader reader1 = spy(new Reader("Reader 1", lock.readLock())); + Writer writer1 = spy(new Writer("Writer 1", lock.writeLock())); + + executeService.submit(reader1); + // Let reader1 execute first + Thread.sleep(50); + executeService.submit(writer1); + + verify(reader1, timeout(99).atLeastOnce()).read(); + verify(writer1, after(10).never()).write(); + verify(writer1, timeout(100).atLeastOnce()).write(); + + } + + /** + * Verify reader and writer can only get the lock to read and write orderly + */ + @Test + public void testWriteAndRead() throws Exception { + + ExecutorService executeService = Executors.newFixedThreadPool(2); + ReaderWriterLock lock = new ReaderWriterLock(); + + Reader reader1 = spy(new Reader("Reader 1", lock.readLock())); + Writer writer1 = spy(new Writer("Writer 1", lock.writeLock())); + + executeService.submit(writer1); + // Let reader1 execute first + Thread.sleep(50); + executeService.submit(reader1); + + verify(writer1, timeout(99).atLeastOnce()).write(); + verify(reader1, after(10).never()).read(); + verify(reader1, timeout(100).atLeastOnce()).read(); + + + } + + @After + public void tearDown() { + executeService.shutdown(); + try { + executeService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + System.out.println("Error waiting for ExecutorService shutdown"); + } + } +} + diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderTest.java new file mode 100644 index 000000000..c18bc1b2b --- /dev/null +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/ReaderTest.java @@ -0,0 +1,45 @@ +package com.iluwatar.reader.writer.lock; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +/** + * @author hongshuwei@gmail.com + */ +public class ReaderTest { + + /** + * Verify that multiple readers can get the read lock concurrently + */ + @Test + public void testRead() throws Exception { + + ExecutorService executeService = Executors.newFixedThreadPool(2); + ReaderWriterLock lock = new ReaderWriterLock(); + + Reader reader1 = spy(new Reader("Reader 1", lock.readLock())); + Reader reader2 = spy(new Reader("Reader 2", lock.readLock())); + + executeService.submit(reader1); + executeService.submit(reader2); + + // Read operation will hold the read lock 100 milliseconds, so here we guarantee that each + // readers can read in 99 milliseconds to prove that multiple read can perform in the same time. + verify(reader1, timeout(99).atLeastOnce()).read(); + verify(reader2, timeout(99).atLeastOnce()).read(); + + executeService.shutdown(); + try { + executeService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + System.out.println("Error waiting for ExecutorService shutdown"); + } + } +} diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/WriterTest.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/WriterTest.java new file mode 100644 index 000000000..c81a56dc1 --- /dev/null +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/WriterTest.java @@ -0,0 +1,52 @@ +package com.iluwatar.reader.writer.lock; + +import static org.mockito.Mockito.after; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +/** + * @author hongshuwei@gmail.com + */ +public class WriterTest { + + /** + * Verify that multiple writers will get the lock in order. + */ + @Test + public void testWrite() throws Exception { + + ExecutorService executeService = Executors.newFixedThreadPool(2); + ReaderWriterLock lock = new ReaderWriterLock(); + + Writer writer1 = spy(new Writer("Writer 1", lock.writeLock())); + Writer writer2 = spy(new Writer("Writer 2", lock.writeLock())); + + executeService.submit(writer1); + // Let write1 execute first + Thread.sleep(50); + executeService.submit(writer2); + + // Write operation will hold the write lock 100 milliseconds, so here we verify that when two + // write excute concurrently + // 1. The first write will get the lock and and write in 60ms + // 2. The second writer will cannot get the lock when first writer get the lock + // 3. The second writer will get the lock as last + verify(writer1, timeout(10).atLeastOnce()).write(); + verify(writer2, after(10).never()).write(); + verify(writer2, timeout(100).atLeastOnce()).write(); + + executeService.shutdown(); + try { + executeService.awaitTermination(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + System.out.println("Error waiting for ExecutorService shutdown"); + } + } +}