본문 바로가기

else if (개발)

Collections.synchronizedCollection 메소드

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 팩토리 메소드를 제공합니다. 쓰는 방법은 크게 다르지 않습니다.

이 글은 스프링노트에서 작성되었습니다.