Read-Write Lock 패턴

패턴 명칭

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

JSON에 대한 Java 객체 직렬화, GSON

구글의 GSON은 JSON 데이터를 Java 객체로 생성해주는 라이브러리입니다.

GSON에 대한 jar 라이브러리를 참조(필자는 gson-2.3.1.jar를 사용)합니다. 먼저 첫번째 예제는 JSON 문자열 대한 객체 직렬화입니다.

String strJSON = "{'name': 'Dip2K', 'age': 44 }";

Gson gson = new GsonBuilder().create();
Person person = gson.fromJson(strJSON, Person.class);
System.out.println(person);

strJSON에 담긴 JSON 데이터를 Person이라는 객체로 생성하고 있습니다. Person은 새롭게 정의한 클래스로 다음과 같습니다.

package tstThread;

import java.util.ArrayList;
import java.util.HashMap;

public class Person {
    private String name;
    private int age;
	
    private Car car = new Car("TEST");
	
    public Person(String name, int age) {	
        this.name = name;
        this.age = age;
    }
	
    @Override
    public String toString() {
        return "[" + name + ", " + age + "]";
    }
}

실행 결과는 다음과 같습니다.

[Dip2K, 44]

다음은 자바 객체를 JSON 데이터로 역직렬화하는 코드예입니다.

Person person = new Person("Dip2K", 44);

Gson gson = new GsonBuilder().create();
String strJson = gson.toJson(person);

System.out.println(strJson);

위의 코드에서 사용된 Person 클래스는 정의는 다음과 같습니다.

package tstThread;

import java.util.ArrayList;
import java.util.HashMap;

public class Person {
    private String name;
    private int age;
	
    private Car car = new Car("TEST");
	
    private ArrayList<String> array = new ArrayList<String>();
    private HashMap<String, Integer> map = new HashMap<String, Integer>();
	
    public Person(String name, int age) {	
        this.name = name;
        this.age = age;
		
        array.add("Card1");
        array.add("Card2");
        array.add("Card3");
		
        map.put("KEY1", 100);
        map.put("KEY2", 200);
        map.put("KEY3", 300);
    }
	
    @Override
    public String toString() {
        return "[" + name + ", " + age + ", " + array.get(1) + "]";
    }
}

새롭게 정의된 Person를 보면 내부 필드 객체로 Car 클래스 타입의 객체를 하나 더 갖고 있는데, 이 Car 클래스는 다음과 같습니다.

package tstThread;

public class Car {
    private String name;
	
    public Car(String name) {
        this.name = name;
    }
}

이 예제는 단순한 클래스 객체 뿐만이 아나리 객체 안에 또 다른 객체가 담겨 있을때에 대한 GSON의 역직렬화가 가능하다는 것을 확인하기 위함입니다. 실행해 보면 다음과 같습니다.

{"name":"Dip2K","age":44,"car":{"name":"TEST"},"array":["Card1","Card2","Card3"],"map":{"KEY2":200,"KEY1":100,"KEY3":300}}

이제 위의 예제에서 얻는 JSON 문자열을 다시 Person 객체로 직열화하는 예제입니다.

String strJSON = "{'name':'Dip2K','age':44,'car':{'name':'TEST'},'array':['Card1','Card2','Card3'],'map':{'KEY2':200,'KEY1':100,'KEY3':300}}";

Gson gson = new GsonBuilder().create();
Person person = gson.fromJson(strJSON, Person.class);
System.out.println(person);

결과는 다음처럼 직열화가 잘된것을 확인할 수 있습니다.

[Dip2K, 44, Card2]