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 af7b5df2c..42335f313 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 @@ -23,14 +23,15 @@ package com.iluwatar.reader.writer.lock; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.stream.IntStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * * In a multiple thread applications, the threads may try to synchronize the shared resources @@ -62,21 +63,42 @@ public class App { ExecutorService executeService = Executors.newFixedThreadPool(10); ReaderWriterLock lock = new ReaderWriterLock(); - - // Start 5 readers + + // Start writers IntStream.range(0, 5) - .forEach(i -> executeService.submit(new Reader("Reader " + i, lock.readLock()))); + .forEach(i -> executeService.submit(new Writer("Writer " + i, lock.writeLock(), + ThreadLocalRandom.current().nextLong(5000)))); + LOGGER.info("Writers added..."); - // Start 5 writers + // Start readers IntStream.range(0, 5) - .forEach(i -> executeService.submit(new Writer("Writer " + i, lock.writeLock()))); + .forEach(i -> executeService.submit(new Reader("Reader " + i, lock.readLock(), + ThreadLocalRandom.current().nextLong(10)))); + LOGGER.info("Readers added..."); + + try { + Thread.sleep(5000L); + } catch (InterruptedException e) { + LOGGER.error("Error sleeping before adding more readers", e); + Thread.currentThread().interrupt(); + } + + // Start readers + IntStream.range(6, 10) + .forEach(i -> executeService.submit(new Reader("Reader " + i, lock.readLock(), + ThreadLocalRandom.current().nextLong(10)))); + LOGGER.info("More readers added..."); + + + // 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) { - LOGGER.error("Error waiting for ExecutorService shutdown"); + LOGGER.error("Error waiting for ExecutorService shutdown", e); + Thread.currentThread().interrupt(); } } 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 index 006aff7d7..b0ccecaba 100644 --- 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 @@ -22,11 +22,11 @@ */ package com.iluwatar.reader.writer.lock; +import java.util.concurrent.locks.Lock; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.locks.Lock; - /** * Reader class, read when it acquired the read lock */ @@ -37,10 +37,30 @@ public class Reader implements Runnable { private Lock readLock; private String name; + + private long readingTime; - public Reader(String name, Lock readLock) { + /** + * Create new Reader + * + * @param name - Name of the thread owning the reader + * @param readLock - Lock for this reader + * @param readingTime - amount of time (in milliseconds) for this reader to engage reading + */ + public Reader(String name, Lock readLock, long readingTime) { this.name = name; this.readLock = readLock; + this.readingTime = readingTime; + } + + /** + * Create new Reader who reads for 250ms + * + * @param name - Name of the thread owning the reader + * @param readLock - Lock for this reader + */ + public Reader(String name, Lock readLock) { + this(name, readLock, 250L); } @Override @@ -49,7 +69,8 @@ public class Reader implements Runnable { try { read(); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.info("InterruptedException when reading", e); + Thread.currentThread().interrupt(); } finally { readLock.unlock(); } @@ -61,7 +82,7 @@ public class Reader implements Runnable { */ public void read() throws InterruptedException { LOGGER.info("{} begin", name); - Thread.sleep(250); - LOGGER.info("{} finish", name); + Thread.sleep(readingTime); + LOGGER.info("{} finish after reading {}ms", name, readingTime); } } 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 e7b3c4451..b377b2273 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 @@ -29,13 +29,18 @@ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * 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. + * 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 static final Logger LOGGER = LoggerFactory.getLogger(ReaderWriterLock.class); private Object readerMutex = new Object(); @@ -45,10 +50,10 @@ public class ReaderWriterLock implements ReadWriteLock { /** * 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. + * 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. */ @@ -74,13 +79,6 @@ public class ReaderWriterLock implements ReadWriteLock { 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 * @@ -89,14 +87,6 @@ public class ReaderWriterLock implements ReadWriteLock { return globalMutex.isEmpty(); } - private static 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 */ @@ -104,31 +94,35 @@ public class ReaderWriterLock implements ReadWriteLock { @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); - } - } - } - + acquireForReaders(); } } } + /** + * Acquire the globalMutex lock on behalf of current and future concurrent readers. Make sure no writers currently + * owns the lock. + */ + private void acquireForReaders() { + // Try to get the globalMutex lock for the first reader + synchronized (globalMutex) { + // 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. + while (doesWriterOwnThisLock()) { + try { + globalMutex.wait(); + } catch (InterruptedException e) { + LOGGER.info("InterruptedException while waiting for globalMutex in acquireForReaders", e); + Thread.currentThread().interrupt(); + } + } + globalMutex.add(this); + } + } + @Override public void unlock() { @@ -179,23 +173,17 @@ public class ReaderWriterLock implements ReadWriteLock { 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"); + // Wait until the lock is free. + while (!isLockFree()) { + try { + globalMutex.wait(); + } catch (InterruptedException e) { + LOGGER.info("InterruptedException while waiting for globalMutex to begin writing", e); + Thread.currentThread().interrupt(); } } + // When the lock is free, acquire it by placing an entry in globalMutex + globalMutex.add(this); } } 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 index c468a61f8..dc379eef9 100644 --- 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 @@ -22,11 +22,11 @@ */ package com.iluwatar.reader.writer.lock; +import java.util.concurrent.locks.Lock; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.concurrent.locks.Lock; - /** * Writer class, write when it acquired the write lock */ @@ -37,10 +37,30 @@ public class Writer implements Runnable { private Lock writeLock; private String name; + + private long writingTime; + /** + * Create new Writer who writes for 250ms + * + * @param name - Name of the thread owning the writer + * @param writeLock - Lock for this writer + */ public Writer(String name, Lock writeLock) { + this(name, writeLock, 250L); + } + + /** + * Create new Writer + * + * @param name - Name of the thread owning the writer + * @param writeLock - Lock for this writer + * @param writingTime - amount of time (in milliseconds) for this reader to engage writing + */ + public Writer(String name, Lock writeLock, long writingTime) { this.name = name; this.writeLock = writeLock; + this.writingTime = writingTime; } @@ -50,18 +70,19 @@ public class Writer implements Runnable { try { write(); } catch (InterruptedException e) { - e.printStackTrace(); + LOGGER.info("InterruptedException when writing", e); + Thread.currentThread().interrupt(); } finally { writeLock.unlock(); } } - + /** * Simulate the write operation */ public void write() throws InterruptedException { LOGGER.info("{} begin", name); - Thread.sleep(250); - LOGGER.info("{} finish", name); + Thread.sleep(writingTime); + LOGGER.info("{} finished after writing {}ms", name, writingTime); } } diff --git a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/utils/InMemoryAppender.java b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/utils/InMemoryAppender.java index 30624a650..b8ad531ce 100644 --- a/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/utils/InMemoryAppender.java +++ b/reader-writer-lock/src/test/java/com/iluwatar/reader/writer/lock/utils/InMemoryAppender.java @@ -52,6 +52,6 @@ public class InMemoryAppender extends AppenderBase { } public boolean logContains(String message) { - return log.stream().anyMatch(event -> event.getFormattedMessage().equals(message)); + return log.stream().anyMatch(event -> event.getFormattedMessage().contains(message)); } }