패턴 명칭
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