패턴 명칭
Read-Write Lock
필요한 상황
Database에서 어떤 테이블이 있다고 하자. 이 테이블은 다수의 클라이언트에서 동시에 읽고 쓰이는 대상이다. 어떤 클라이언트는 이 테이블에 데이터를 쓰고, 어떤 또 다른 클라이언트는 이 테이블에서 데이터를 읽는다. 그런데 만약 2개의 클라이언트에서 특정 레코드 데이터를 동시에 각각 쓰고 읽기를 수행하면 읽는 쓰레드 쪽에서는 망가진 데이터를 읽을 수 있다. 또한 만약 2개의 클라이언트에서 동시에 특정 레코드 데이터를 쓰려고 할때 망가진 데이터가 저장될 수 있다. 문제가 없는 경우는 2개의 클라이언트가 동시에 특정 레코드의 데이터를 읽을 때 뿐이다. 이 패턴은 어떤 데이터에 대해 동시에 읽고 쓸때의 상황에서, 또는 동시에 데이터를 쓰는 상황에서 문제가 발생하지 않도록 해주는데 목적이 있다.
예제 코드

위의 클래스 다이어그램에서 언급된 클래스 중 Reader와 Writer는 각각 어떤 데이터를 읽고 쓰는 스레드이며, Data는 이 쓰레드가 읽거나 쓰는 대상이 되는 데이터를 담고 있는 클래스이다. Lock은 읽기와 쓰기에 대한 스레드 제어를 위한 클래스이다. 이 클래스들 사용하는 예제 코드는 다음과 같다.
package tstThread;
public class Main {
public static void main(String[] args) {
Data data = new Data(10);
new Reader(data).start();
new Reader(data).start();
new Reader(data).start();
String[] weeks = {"월", "화", "수", "목", "금", "토", "일"};
new Writer(data, weeks).start();
String[] numbers = {"ONE", "TWO", "THREE", "FOUR", "FIVE", "SIX", "SEVEN", "EIGHT", "NINE"};
new Writer(data, numbers).start();
String[] digits = {"1", "2", "3", "4", "5", "6", "7", "8", "9"};
new Writer(data, digits).start();
}
}
Reader 클래스는 다음과 같다.
package tstThread;
public class Reader extends Thread {
private final Data data;
public Reader(Data data) {
this.data = data;
}
public void run() {
try {
while(true) {
String v = data.read();
System.out.println(Thread.currentThread().getName() + " -> " + v);
}
} catch(InterruptedException e) {
//.
}
}
}
Writer 클래스는 다음과 같다.
package tstThread;
import java.util.Random;
public class Writer extends Thread {
private static final Random random = new Random();
private final Data data;
private final String[] inputs;
private int index = 0;
public Writer(Data data, String[] inputs) {
this.data = data;
this.inputs = inputs;
}
public void run() {
try {
while(true) {
String input = inputs[index];
index = (index + 1) % inputs.length;
data.write(input);
Thread.sleep(random.nextInt(1000));
}
} catch(InterruptedException e) {
//.
}
}
}
Data 클래스는 다음과 같다.
package tstThread;
public class Data {
private final StringBuilder buffer;
private final Lock lock = new Lock();
public Data(int size) {
this.buffer = new StringBuilder("#empty");
}
public String read() throws InterruptedException {
lock.readLock();
try {
Thread.sleep(100);
return buffer.toString();
} finally {
lock.readUnlock();
}
}
public void write(String v) throws InterruptedException {
lock.writeLock();
try {
Thread.sleep(100);
buffer.setLength(0);
buffer.append(v);
} finally {
lock.writeUnlock();
}
}
}
Lock 클래스는 다음과 같다.
package tstThread;
public class Lock {
private int readingReaders = 0;
private int waitingReaders = 0;
private boolean preferReader = false;
private int waitingWriters = 0;
private int writingWriters = 0;
private boolean preferWriter = true;
public synchronized void readLock() throws InterruptedException {
waitingReaders++;
try {
while(writingWriters > 0 || (preferWriter && waitingWriters > 0)) {
wait();
}
} finally {
waitingReaders--;
}
readingReaders++;
}
public synchronized void readUnlock() {
readingReaders--;
preferWriter = true;
preferReader = false;
notifyAll();
}
public synchronized void writeLock() throws InterruptedException {
waitingWriters++;
try {
while(readingReaders > 0 || writingWriters > 0 || (preferReader && waitingReaders > 0)) {
wait();
}
} finally {
waitingWriters--;
}
writingWriters++;
}
public synchronized void writeUnlock() {
writingWriters--;
preferWriter = false;
preferReader = true;
notifyAll();
}
}
실행 결과는 다음과 같다.
Thread-2 -> #empty Thread-1 -> #empty Thread-0 -> #empty Thread-0 -> 월 Thread-2 -> 월 Thread-1 -> 월 Thread-0 -> 1 . . . Thread-0 -> 3 Thread-0 -> FOUR Thread-2 -> FOUR Thread-1 -> FOUR
