#354 Add maven model for feature toggle design pattern

This commit is contained in:
Joseph McCarthy
2016-01-25 21:14:24 +00:00
parent cf10bd1d05
commit d7526fc7c0
86 changed files with 1165 additions and 270 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View 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>

View 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.
![alt text](./etc/reader-writer-lock.png "Reader writer lock")
**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**
* [Readerswriter lock](https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock)
* [Readerswriters_problem](https://en.wikipedia.org/wiki/Readers%E2%80%93writers_problem)

View 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>

View File

@@ -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");
}
}
}

View File

@@ -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");
}
}

View File

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

View File

@@ -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");
}
}

View File

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

View File

@@ -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");
}
}

View File

@@ -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");
}
}

View File

@@ -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;
}
}

View File

@@ -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");
}
}