implements the #75 reader writer lock, fix the problem of review
This commit is contained in:
parent
2f84369003
commit
77d45c35e0
Binary file not shown.
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 39 KiB |
@ -1,52 +1,52 @@
|
||||
<?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.ReaderWriterLock.ReaderLock"
|
||||
<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="-1" width="-1" x="305" y="511"/>
|
||||
<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="2" language="java" name="com.iluwatar.reader.writer.lock.ReaderWriterLock" project="reader-writer-lock"
|
||||
<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="-1" width="-1" x="394" y="270"/>
|
||||
<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="3" language="java" name="com.iluwatar.reader.writer.lock.App" project="reader-writer-lock"
|
||||
file="/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/App.java" binary="false"
|
||||
<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="671" y="274"/>
|
||||
<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>
|
||||
<interface id="4" language="java" name="com.iluwatar.reader.writer.lock.Lock" project="reader-writer-lock"
|
||||
file="/reader-writer-lock/src/main/java/com/iluwatar/reader/writer/lock/Lock.java" binary="false"
|
||||
corner="BOTTOM_RIGHT">
|
||||
<position height="101" width="148" x="322" y="597"/>
|
||||
<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>
|
||||
</interface>
|
||||
<class id="5" language="java" name="com.iluwatar.reader.writer.lock.ReaderWriterLock.WriterLock"
|
||||
<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="-1" width="-1" x="488" y="509"/>
|
||||
<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"/>
|
||||
@ -54,36 +54,28 @@
|
||||
</display>
|
||||
</class>
|
||||
<nesting id="6">
|
||||
<end type="SOURCE" refId="2"/>
|
||||
<end type="TARGET" refId="5"/>
|
||||
<end type="SOURCE" refId="3"/>
|
||||
<end type="TARGET" refId="2"/>
|
||||
</nesting>
|
||||
<realization id="7">
|
||||
<end type="SOURCE" refId="5"/>
|
||||
<end type="TARGET" refId="4"/>
|
||||
</realization>
|
||||
<realization id="8">
|
||||
<end type="SOURCE" refId="1"/>
|
||||
<end type="TARGET" refId="4"/>
|
||||
</realization>
|
||||
<association id="9">
|
||||
<end type="SOURCE" refId="2" navigable="false">
|
||||
<attribute id="10" name="writeLock"/>
|
||||
<multiplicity id="11" minimum="0" maximum="1"/>
|
||||
<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>
|
||||
<association id="12">
|
||||
<end type="SOURCE" refId="2" navigable="false">
|
||||
<attribute id="13" name="readLock"/>
|
||||
<multiplicity id="14" minimum="0" maximum="1"/>
|
||||
</end>
|
||||
<end type="TARGET" refId="1" navigable="true"/>
|
||||
<display labels="true" multiplicity="true"/>
|
||||
</association>
|
||||
<nesting id="15">
|
||||
<end type="SOURCE" refId="2"/>
|
||||
<end type="TARGET" refId="1"/>
|
||||
<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">
|
||||
|
@ -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:
|
||||
|
@ -1,18 +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.9.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>reader-writer-lock</artifactId>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<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>
|
||||
|
@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
*
|
||||
* <p>
|
||||
* 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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
@ -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.
|
||||
* <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 Object r = new Object();
|
||||
|
||||
/**
|
||||
* Global mutex for reader or writer, use to save the holding object
|
||||
*/
|
||||
private Set<Object> g = new HashSet<>();
|
||||
|
||||
/**
|
||||
* Current reader count
|
||||
*/
|
||||
private int readerCount = 0;
|
||||
|
||||
private ReaderLock readLock = new ReaderLock();
|
||||
private WriterLock writeLock = new WriterLock();
|
||||
private Set<Object> 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user