Java Collection Framework의 대부분의 Collection 구현체들은 Thread-Safe하지 않으므로 멀티 스레드 환경이라면 반드시 synchronized block을 적절하게 잡아 주어야 합니다.
그런데, java.util.Collections 클래스의 static 팩토리 메소드인 Collections.synchronizedCollection 메소드를 이용하면 간편하게 Thread-Safe한 Collection 객체를 생성할 수 있습니다.
아래의 코드는 10000개의 스레드에서 1개의 TreeSet에 동시 접근해 Integer 객체의 삽입/삭제를 수행하는 코드로, 심각한 문제를 일으키는 코드입니다.
import java.util.Collection;
import java.util.TreeSet;
public class SynchronizedCollectionEx {
//TreeSet은 Thread-Safe하지 않습니다.
static Collection treeSet = new TreeSet();
public static void main(String[] args){
Thread[] t = new Thread[10000];
for (int i = 0; i < 10000; i++){
t[i] = new Thread(new WriterThread());
t[i].start();
}
}
}
class WriterThread implements Runnable {
public void run() {
for (int i = 0; i < 100; i++){
SynchronizedCollectionEx.treeSet.add(new Integer((int)(Math.random() * 10)));
SynchronizedCollectionEx.treeSet.remove(new Integer((int)(Math.random() * 10)));
}
}
}
위 코드를 컴파일 - 실행하면 다음과 같은 예외가 줄기차게 발생하고, 급기야 deadlock에 빠지기도 합니다. 단일 프로세서 환경에서 실행했을 때는 그렇게 자주 발생하지는 않더군요. 하지만 듀얼 코어에서 돌리니 거의 항상 발생했습니다.
Exception in thread "Thread-119" java.lang.NullPointerException
at java.util.TreeMap.fixAfterInsertion(TreeMap.java:2074)
at java.util.TreeMap.put(TreeMap.java:559)
at java.util.TreeSet.add(TreeSet.java:238)
at WriterThread.run(Test.java:34)
at java.lang.Thread.run(Thread.java:619)
Exception in thread "Thread-732" java.lang.NullPointerException
at java.util.TreeMap.fixAfterInsertion(TreeMap.java:2074)
at java.util.TreeMap.put(TreeMap.java:559)
at java.util.TreeSet.add(TreeSet.java:238)
at WriterThread.run(Test.java:34)
at java.lang.Thread.run(Thread.java:619)
...
코드에서 공유 객체를 쓰거나 읽는 부분에 synchronized 블럭을 잘 잡아주어야 해결되는 문제입니다. 그리고, 또 하나의 간편한 해결책이 공유 객체인 treeSet 객체를 Thread-Safe한 객체로 재탄생 시키는 방법입니다. 이때 이용하는 static 메소드가
Collections.synchronizedCollection(Collection c)
입니다. 이 메소드는 인자로 들어온 Collection 객체를 래핑하여 Thread-Safe한 collection 객체를 반환합니다.
import java.util.Collection;
import java.util.Collections;
import java.util.TreeSet;
public class SynchronizedCollectionEx {
//treeSet은 Thread-Safe 하지 않습니다.
static Collection treeSet = new TreeSet();
//synchronizedSet은 Thread-Safe 합니다. 이 객체를 공유 객체로 사용해야 합니다.
static Collection synchronizedSet = Collections.synchronizedCollection(treeSet);
public static void main(String[] args){
Thread[] t = new Thread[10000];
for (int i = 0; i < 10000; i++){
t[i] = new Thread(new WriterThread());
t[i].start();
}
}
}
class WriterThread implements Runnable {
public void run() {
for (int i = 0; i < 100; i++){
SynchronizedCollectionEx.synchronizedSet.add(new Integer((int)(Math.random() * 10)));
SynchronizedCollectionEx.synchronizedSet.remove(new Integer((int)(Math.random() * 10)));
}
}
}
java.util.Collections 클래스에는 synchronizedCollections 뿐만 아니라, SynchronizedSet, SynchronizedMap 등등 많은 Synchronized... 류의 static 팩토리 메소드를 제공합니다. 쓰는 방법은 크게 다르지 않습니다.
이 글은 스프링노트에서 작성되었습니다.