|
8 | 8 |
|
9 | 9 | ## Intent |
10 | 10 |
|
11 | | -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. |
| 11 | +Regular lock does not distinguish the ‘read lock’ and ‘write lock’, as when access the data structure patterns |
| 12 | +consists of many threads reading the data, each thread will have to lock it which produces unnecessary serialization. |
| 13 | +The existence of reader-writer lock resolves this issue as it is well known as |
| 14 | +“multiple concurrent readers, single writer locks”, used to consist of multiple threads reading the data concurrently |
| 15 | +and allow only one thread to write or modify the data. All others (readers or writers) will be blocked while the writer |
| 16 | +is modifying or writing the data and unblocked until the writer finishes writing. |
| 17 | + |
| 18 | +## Explanation |
| 19 | + |
| 20 | +Real world example |
| 21 | + |
| 22 | +> Consider if we obtain a database for bank accounts. If Alice wants to transfer from account1 to account2, at the |
| 23 | +> same time Bob transfer money from account2 to account3. Alice will first read the totals of account1 and account2. Then, Bob's |
| 24 | +> transaction executed completely. Alice is now working with outdated values, therefore, the total amount in account2 |
| 25 | +> would be incorrect. With transactions, Bob would have to wait until Alice finishes her process of the accounts. |
| 26 | +
|
| 27 | + |
| 28 | + |
| 29 | + |
| 30 | +In plain words |
| 31 | + |
| 32 | +> Reader-writer lock enables either multiple readers or single writer to hold the lock at any given time. |
| 33 | +
|
| 34 | + |
| 35 | +Wikipedia says |
| 36 | +> In computer science, a readers–writer (single-writer lock, a multi-reader lock, a push lock, or an MRSW lock) |
| 37 | +> is a synchronization primitive that solves one of the readers–writers problems. |
| 38 | +
|
| 39 | + |
| 40 | +**Programmatic Example** |
| 41 | + |
| 42 | +In our programmatic example, we demonstrate the implementation of the access to either reader or writer. |
| 43 | +We first create a `Reader` class which read when it acquired the read lock. It creates the reader and simulate the read operation. |
| 44 | +```java |
| 45 | +@Slf4j |
| 46 | +public class Reader implements Runnable { |
| 47 | + |
| 48 | + private Lock readLock; |
| 49 | + |
| 50 | + private String name; |
| 51 | + |
| 52 | + private long readingTime; |
| 53 | + |
| 54 | + public Reader(String name, Lock readLock, long readingTime) { |
| 55 | + this.name = name; |
| 56 | + this.readLock = readLock; |
| 57 | + this.readingTime = readingTime; |
| 58 | + } |
| 59 | + |
| 60 | + public Reader(String name, Lock readLock) { |
| 61 | + this(name, readLock, 250L); |
| 62 | + } |
| 63 | + |
| 64 | + |
| 65 | + @Override |
| 66 | + public void run() { |
| 67 | + readLock.lock(); |
| 68 | + try { |
| 69 | + read(); |
| 70 | + } catch (InterruptedException e) { |
| 71 | + LOGGER.info("InterruptedException when reading", e); |
| 72 | + Thread.currentThread().interrupt(); |
| 73 | + } finally { |
| 74 | + readLock.unlock(); |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + public void read() throws InterruptedException { |
| 79 | + LOGGER.info("{} begin", name); |
| 80 | + Thread.sleep(readingTime); |
| 81 | + LOGGER.info("{} finish after reading {}ms", name, readingTime); |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | + |
| 86 | +``` |
| 87 | + |
| 88 | + |
| 89 | +In the `Writer` class, we operate write when it acquired the writer lock. It follows the similar process as the `Reader` class |
| 90 | +which creates the writer and simulate the write operation. |
| 91 | +```java |
| 92 | + |
| 93 | +public class Writer implements Runnable { |
| 94 | + |
| 95 | + private final Lock writeLock; |
| 96 | + private final String name; |
| 97 | + private final long writingTime; |
| 98 | + |
| 99 | + public Writer(String name, Lock writeLock) { |
| 100 | + this(name, writeLock, 250L); |
| 101 | + } |
| 102 | + |
| 103 | + public Writer(String name, Lock writeLock, long writingTime) { |
| 104 | + this.name = name; |
| 105 | + this.writeLock = writeLock; |
| 106 | + this.writingTime = writingTime; |
| 107 | + } |
| 108 | + |
| 109 | + @Override |
| 110 | + public void run() { |
| 111 | + writeLock.lock(); |
| 112 | + try { |
| 113 | + write(); |
| 114 | + } catch (InterruptedException e) { |
| 115 | + LOGGER.info("InterruptedException when writing", e); |
| 116 | + Thread.currentThread().interrupt(); |
| 117 | + } finally { |
| 118 | + writeLock.unlock(); |
| 119 | + } |
| 120 | + } |
| 121 | + |
| 122 | + public void write() throws InterruptedException { |
| 123 | + LOGGER.info("{} begin", name); |
| 124 | + Thread.sleep(writingTime); |
| 125 | + LOGGER.info("{} finished after writing {}ms", name, writingTime); |
| 126 | + } |
| 127 | +} |
| 128 | + |
| 129 | + |
| 130 | + |
| 131 | +``` |
| 132 | + |
| 133 | + |
| 134 | +Now, in the `ReadWriteLock` class which would take the responsibilities to control the access for either the reader or the writer. |
| 135 | +In the `ReadLock` class, it restricts that if there's no writer that gets the lock, then multiple readers could be access concurrently. |
| 136 | +In the `WriteLock` class, it restricts that only one writer could be accessed. |
| 137 | + |
| 138 | +```java |
| 139 | + |
| 140 | +public class ReadWriteLock implements ReaderWriterLock { |
| 141 | + |
| 142 | + private final Object readerMutex = new Object(); |
| 143 | + private int currentReaderCount; |
| 144 | + |
| 145 | + |
| 146 | + private final Set<Object> globalMutex = new HashSet<>(); |
| 147 | + private final ReadLock readerLock = new ReadLock(); |
| 148 | + private final WriteLock writerLock = new WriteLock(); |
| 149 | + |
| 150 | + |
| 151 | + public Lock readLock() { |
| 152 | + return readerLock; |
| 153 | + } |
| 154 | + |
| 155 | + public Lock writeLock() { |
| 156 | + return writerLock; |
| 157 | + } |
| 158 | + |
| 159 | + private boolean doesWriterOwnThisLock() { |
| 160 | + return globalMutex.contains(writerLock); |
| 161 | + } |
| 162 | + |
| 163 | + private boolean isLockFree() { |
| 164 | + return globalMutex.isEmpty(); |
| 165 | + } |
| 166 | + |
| 167 | + |
| 168 | + private class ReadLock implements Lock { |
| 169 | + |
| 170 | + @Override |
| 171 | + public void lock() { |
| 172 | + synchronized (readerMutex) { |
| 173 | + currentReaderCount++; |
| 174 | + if (currentReaderCount == 1) { |
| 175 | + acquireForReaders(); |
| 176 | + } |
| 177 | + } |
| 178 | + } |
| 179 | + |
| 180 | + @Override |
| 181 | + public void unlock() { |
| 182 | + synchronized (readerMutex) { |
| 183 | + currentReaderCount--; |
| 184 | + |
| 185 | + if (currentReaderCount == 0) { |
| 186 | + synchronized (globalMutex) { |
| 187 | + globalMutex.remove(this); |
| 188 | + globalMutex.notifyAll(); |
| 189 | + } |
| 190 | + } |
| 191 | + } |
| 192 | + |
| 193 | + } |
| 194 | + } |
| 195 | + |
| 196 | + |
| 197 | + private class WriteLock implements Lock { |
| 198 | + |
| 199 | + @Override |
| 200 | + public void lock() { |
| 201 | + synchronized (globalMutex) { |
| 202 | + while (!isLockFree()) { |
| 203 | + try { |
| 204 | + globalMutex.wait(); |
| 205 | + } catch (InterruptedException e) { |
| 206 | + LOGGER.info("InterruptedException while waiting for globalMutex to begin writing", e); |
| 207 | + Thread.currentThread().interrupt(); |
| 208 | + } |
| 209 | + } |
| 210 | + globalMutex.add(this); |
| 211 | + } |
| 212 | + } |
| 213 | + |
| 214 | + @Override |
| 215 | + public void unlock() { |
| 216 | + synchronized (globalMutex) { |
| 217 | + globalMutex.remove(this); |
| 218 | + globalMutex.notifyAll(); |
| 219 | + } |
| 220 | + } |
| 221 | + |
| 222 | + } |
| 223 | +} |
| 224 | + |
| 225 | + |
| 226 | + |
| 227 | + |
| 228 | + |
| 229 | +``` |
| 230 | + |
12 | 231 |
|
13 | 232 | ## Class diagram |
14 | 233 |  |
15 | 234 |
|
16 | 235 | ## Applicability |
17 | 236 |
|
18 | | -Application need to increase the performance of resource synchronize for multiple thread, in particularly there are mixed read/write operations. |
| 237 | +Use the Reader-writer lock when: |
| 238 | +* You need to increase the performance of resource synchronize for multiple thread, in particularly there are mixed read/write operations. |
| 239 | +* In the bank transaction system, you want to ensure when two users are transacting the same account, one people will wait until the other finishes. |
| 240 | + |
| 241 | + |
19 | 242 |
|
20 | | -## Real world examples |
21 | 243 |
|
22 | | -* [Java Reader Writer Lock](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReadWriteLock.html) |
23 | 244 |
|
24 | 245 | ## Credits |
25 | 246 |
|
26 | 247 | * [Readers–writer lock](https://en.wikipedia.org/wiki/Readers%E2%80%93writer_lock) |
27 | 248 | * [Readers–writers_problem](https://en.wikipedia.org/wiki/Readers%E2%80%93writers_problem) |
| 249 | + |
0 commit comments