#354 Add maven model for feature toggle design pattern
This commit is contained in:
BIN
reader-writer-lock/etc/reader-writer-lock.png
Normal file
BIN
reader-writer-lock/etc/reader-writer-lock.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 39 KiB |
86
reader-writer-lock/etc/reader-writer-lock.ucls
Normal file
86
reader-writer-lock/etc/reader-writer-lock.ucls
Normal file
@@ -0,0 +1,86 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<class-diagram version="1.1.8" icons="true" automaticImage="PNG" always-add-relationships="false" generalizations="true"
|
||||
realizations="true" associations="true" dependencies="false" nesting-relationships="true">
|
||||
<class id="1" language="java" name="com.iluwatar.reader.writer.lock.Writer" project="reader-writer-lock"
|
||||
file="/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Writer.java" binary="false"
|
||||
corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="487" y="105"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<class id="2" language="java" name="com.iluwatar.reader.writer.lock.ReaderWriterLock.WriteLock"
|
||||
project="reader-writer-lock"
|
||||
file="/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java" binary="false"
|
||||
corner="BOTTOM_RIGHT">
|
||||
<position height="191" width="197" x="488" y="313"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<class id="3" language="java" name="com.iluwatar.reader.writer.lock.ReaderWriterLock" project="reader-writer-lock"
|
||||
file="/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java" binary="false"
|
||||
corner="BOTTOM_RIGHT">
|
||||
<position height="245" width="224" x="606" y="28"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<class id="4" language="java" name="com.iluwatar.reader.writer.lock.Reader" project="reader-writer-lock"
|
||||
file="/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Reader.java" binary="false"
|
||||
corner="BOTTOM_RIGHT">
|
||||
<position height="-1" width="-1" x="944" y="109"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<class id="5" language="java" name="com.iluwatar.reader.writer.lock.ReaderWriterLock.ReadLock"
|
||||
project="reader-writer-lock"
|
||||
file="/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/ReaderWriterLock.java" binary="false"
|
||||
corner="BOTTOM_RIGHT">
|
||||
<position height="191" width="197" x="725" y="313"/>
|
||||
<display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</display>
|
||||
</class>
|
||||
<nesting id="6">
|
||||
<end type="SOURCE" refId="3"/>
|
||||
<end type="TARGET" refId="2"/>
|
||||
</nesting>
|
||||
<association id="7">
|
||||
<end type="SOURCE" refId="3" navigable="false">
|
||||
<attribute id="8" name="writerLock"/>
|
||||
<multiplicity id="9" minimum="0" maximum="1"/>
|
||||
</end>
|
||||
<end type="TARGET" refId="2" navigable="true"/>
|
||||
<display labels="true" multiplicity="true"/>
|
||||
</association>
|
||||
<association id="10">
|
||||
<end type="SOURCE" refId="3" navigable="false">
|
||||
<attribute id="11" name="readerLock"/>
|
||||
<multiplicity id="12" minimum="0" maximum="1"/>
|
||||
</end>
|
||||
<end type="TARGET" refId="5" navigable="true"/>
|
||||
<display labels="true" multiplicity="true"/>
|
||||
</association>
|
||||
<nesting id="13">
|
||||
<end type="SOURCE" refId="3"/>
|
||||
<end type="TARGET" refId="5"/>
|
||||
</nesting>
|
||||
<classifier-display autosize="true" stereotype="true" package="true" initial-value="false" signature="true"
|
||||
sort-features="false" accessors="true" visibility="true">
|
||||
<attributes public="true" package="true" protected="true" private="true" static="true"/>
|
||||
<operations public="true" package="true" protected="true" private="true" static="true"/>
|
||||
</classifier-display>
|
||||
<association-display labels="true" multiplicity="true"/>
|
||||
</class-diagram>
|
29
reader-writer-lock/index.md
Normal file
29
reader-writer-lock/index.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
layout: pattern
|
||||
title: Reader Writer Lock
|
||||
folder: reader-writer-lock
|
||||
permalink: /patterns/reader-writer-lock/
|
||||
categories: Concurrent
|
||||
tags:
|
||||
- Java
|
||||
---
|
||||
|
||||
**Intent:**
|
||||
|
||||
Suppose we have a shared memory area with the basic constraints detailed above. It is possible to protect the shared data behind a mutual exclusion mutex, in which case no two threads can access the data at the same time. However, this solution is suboptimal, because it is possible that a reader R1 might have the lock, and then another reader R2 requests access. It would be foolish for R2 to wait until R1 was done before starting its own read operation; instead, R2 should start right away. This is the motivation for the Reader Writer Lock pattern.
|
||||
|
||||

|
||||
|
||||
**Applicability:**
|
||||
|
||||
Application need to increase the performance of resource synchronize for multiple thread, in particularly there are mixed read/write operations.
|
||||
|
||||
**Real world examples:**
|
||||
|
||||
* [Java Reader Writer Lock](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReadWriteLock.html)
|
||||
|
||||
**Credits**
|
||||
|
||||
* [Readers–writer lock](https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock)
|
||||
|
||||
* [Readers–writers_problem](https://en.wikipedia.org/wiki/Readers%E2%80%93writers_problem)
|
24
reader-writer-lock/pom.xml
Normal file
24
reader-writer-lock/pom.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0"?>
|
||||
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
|
||||
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>com.iluwatar</groupId>
|
||||
<artifactId>java-design-patterns</artifactId>
|
||||
<version>1.10.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>reader-writer-lock</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.mockito</groupId>
|
||||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
</project>
|
@@ -0,0 +1,56 @@
|
||||
package com.iluwatar.reader.writer.lock;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
/**
|
||||
*
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* This example use two mutex to demonstrate the concurrent access of multiple readers and writers.
|
||||
*
|
||||
*
|
||||
* @author hongshuwei@gmail.com
|
||||
*/
|
||||
public class App {
|
||||
|
||||
/**
|
||||
* Program entry point
|
||||
*
|
||||
* @param args command line args
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
|
||||
ExecutorService executeService = Executors.newFixedThreadPool(10);
|
||||
ReaderWriterLock lock = new ReaderWriterLock();
|
||||
|
||||
// Start 5 readers
|
||||
IntStream.range(0, 5)
|
||||
.forEach(i -> executeService.submit(new Reader("Reader " + i, lock.readLock())));
|
||||
|
||||
// Start 5 writers
|
||||
IntStream.range(0, 5)
|
||||
.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 {
|
||||
executeService.awaitTermination(5, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("Error waiting for ExecutorService shutdown");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -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(250);
|
||||
System.out.println(name + " finish");
|
||||
}
|
||||
}
|
@@ -0,0 +1,211 @@
|
||||
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 implements ReadWriteLock {
|
||||
|
||||
|
||||
private Object readerMutex = new Object();
|
||||
|
||||
private int currentReaderCount = 0;
|
||||
|
||||
/**
|
||||
* Global mutex is used to indicate that whether reader or writer gets the lock in the moment.
|
||||
* <p>
|
||||
* 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. <br>
|
||||
* 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.
|
||||
* <p>
|
||||
* This is the most important field in this class to control the access for reader/writer.
|
||||
*/
|
||||
private Set<Object> globalMutex = new HashSet<>();
|
||||
|
||||
private ReadLock readerLock = new ReadLock();
|
||||
private WriteLock writerLock = new WriteLock();
|
||||
|
||||
@Override
|
||||
public Lock readLock() {
|
||||
return readerLock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Lock 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 ReadLock implements Lock {
|
||||
|
||||
@Override
|
||||
public void lock() {
|
||||
|
||||
synchronized (readerMutex) {
|
||||
|
||||
currentReaderCount++;
|
||||
if (currentReaderCount == 1) {
|
||||
// Try to get the globalMutex lock for the first reader
|
||||
synchronized (globalMutex) {
|
||||
while (true) {
|
||||
// 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 {
|
||||
// If lock is acquired by the write, let the thread wait until the writer release
|
||||
// the lock
|
||||
waitUninterruptibly(globalMutex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock() {
|
||||
|
||||
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 WriteLock implements Lock {
|
||||
|
||||
@Override
|
||||
public void lock() {
|
||||
|
||||
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()) {
|
||||
globalMutex.add(this);
|
||||
break;
|
||||
} 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 AssertionError("it should never reach here");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unlock() {
|
||||
|
||||
synchronized (globalMutex) {
|
||||
globalMutex.remove(this);
|
||||
// Notify the waiter, other writer or reader
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -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(250);
|
||||
System.out.println(name + " finish");
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
package com.iluwatar.reader.writer.lock;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Application test
|
||||
*/
|
||||
public class AppTest {
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
String[] args = {};
|
||||
App.main(args);
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,81 @@
|
||||
package com.iluwatar.reader.writer.lock;
|
||||
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
/**
|
||||
* @author hongshuwei@gmail.com
|
||||
*/
|
||||
public class ReaderAndWriterTest extends StdOutTest {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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 = new Reader("Reader 1", lock.readLock());
|
||||
Writer writer1 = new Writer("Writer 1", lock.writeLock());
|
||||
|
||||
ExecutorService executeService = Executors.newFixedThreadPool(2);
|
||||
executeService.submit(reader1);
|
||||
// Let reader1 execute first
|
||||
Thread.sleep(150);
|
||||
executeService.submit(writer1);
|
||||
|
||||
executeService.shutdown();
|
||||
try {
|
||||
executeService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("Error waiting for ExecutorService shutdown");
|
||||
}
|
||||
|
||||
final InOrder inOrder = inOrder(getStdOutMock());
|
||||
inOrder.verify(getStdOutMock()).println("Reader 1 begin");
|
||||
inOrder.verify(getStdOutMock()).println("Reader 1 finish");
|
||||
inOrder.verify(getStdOutMock()).println("Writer 1 begin");
|
||||
inOrder.verify(getStdOutMock()).println("Writer 1 finish");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = new Reader("Reader 1", lock.readLock());
|
||||
Writer writer1 = new Writer("Writer 1", lock.writeLock());
|
||||
|
||||
executeService.submit(writer1);
|
||||
// Let writer1 execute first
|
||||
Thread.sleep(150);
|
||||
executeService.submit(reader1);
|
||||
|
||||
executeService.shutdown();
|
||||
try {
|
||||
executeService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("Error waiting for ExecutorService shutdown");
|
||||
}
|
||||
|
||||
final InOrder inOrder = inOrder(getStdOutMock());
|
||||
inOrder.verify(getStdOutMock()).println("Writer 1 begin");
|
||||
inOrder.verify(getStdOutMock()).println("Writer 1 finish");
|
||||
inOrder.verify(getStdOutMock()).println("Reader 1 begin");
|
||||
inOrder.verify(getStdOutMock()).println("Reader 1 finish");
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,50 @@
|
||||
package com.iluwatar.reader.writer.lock;
|
||||
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
/**
|
||||
* @author hongshuwei@gmail.com
|
||||
*/
|
||||
public class ReaderTest extends StdOutTest {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
Thread.sleep(150);
|
||||
executeService.submit(reader2);
|
||||
|
||||
executeService.shutdown();
|
||||
try {
|
||||
executeService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("Error waiting for ExecutorService shutdown");
|
||||
}
|
||||
|
||||
// Read operation will hold the read lock 250 milliseconds, so here we prove that multiple reads
|
||||
// can be performed in the same time.
|
||||
final InOrder inOrder = inOrder(getStdOutMock());
|
||||
inOrder.verify(getStdOutMock()).println("Reader 1 begin");
|
||||
inOrder.verify(getStdOutMock()).println("Reader 2 begin");
|
||||
inOrder.verify(getStdOutMock()).println("Reader 1 finish");
|
||||
inOrder.verify(getStdOutMock()).println("Reader 2 finish");
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
package com.iluwatar.reader.writer.lock;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.io.PrintStream;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Date: 12/10/15 - 8:37 PM
|
||||
*
|
||||
* @author Jeroen Meulemeester
|
||||
*/
|
||||
public abstract class StdOutTest {
|
||||
|
||||
/**
|
||||
* The mocked standard out {@link PrintStream}, required since some actions don't have any
|
||||
* influence on accessible objects, except for writing to std-out using {@link System#out}
|
||||
*/
|
||||
private final PrintStream stdOutMock = mock(PrintStream.class);
|
||||
|
||||
/**
|
||||
* Keep the original std-out so it can be restored after the test
|
||||
*/
|
||||
private final PrintStream stdOutOrig = System.out;
|
||||
|
||||
/**
|
||||
* Inject the mocked std-out {@link PrintStream} into the {@link System} class before each test
|
||||
*/
|
||||
@Before
|
||||
public void setUp() {
|
||||
System.setOut(this.stdOutMock);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removed the mocked std-out {@link PrintStream} again from the {@link System} class
|
||||
*/
|
||||
@After
|
||||
public void tearDown() {
|
||||
System.setOut(this.stdOutOrig);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mocked stdOut {@link PrintStream}
|
||||
*
|
||||
* @return The stdOut print stream mock, renewed before each test
|
||||
*/
|
||||
final PrintStream getStdOutMock() {
|
||||
return this.stdOutMock;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
package com.iluwatar.reader.writer.lock;
|
||||
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
|
||||
/**
|
||||
* @author hongshuwei@gmail.com
|
||||
*/
|
||||
public class WriterTest extends StdOutTest {
|
||||
|
||||
/**
|
||||
* 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(150);
|
||||
executeService.submit(writer2);
|
||||
|
||||
executeService.shutdown();
|
||||
try {
|
||||
executeService.awaitTermination(10, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
System.out.println("Error waiting for ExecutorService shutdown");
|
||||
}
|
||||
// Write operation will hold the write lock 250 milliseconds, so here we verify that when two
|
||||
// writer execute concurrently, the second writer can only writes only when the first one is
|
||||
// finished.
|
||||
final InOrder inOrder = inOrder(getStdOutMock());
|
||||
inOrder.verify(getStdOutMock()).println("Writer 1 begin");
|
||||
inOrder.verify(getStdOutMock()).println("Writer 1 finish");
|
||||
inOrder.verify(getStdOutMock()).println("Writer 2 begin");
|
||||
inOrder.verify(getStdOutMock()).println("Writer 2 finish");
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user