병렬 쓰레드 프로그래밍(java thread, concurrent)을 할 때, 주의해야 할 사항 중 하나로 꼽히는 부분을 얘기 드리고자 합니다.
모든 java api에서 클래스가 Thread safety한지 Unsafety를 명시적으로 적은 것도 있고 안 쓴 것도 있기 때문에, 병렬 쓰레드 프로그래밍을 할 때는 단순히 thread와 concurrent 패키지의 api을 이해할 뿐 아니라.. 사용하는 클래스의 Thread Safety를 확인해야 합니다.
또한 Thread Safety는 성능을 놓칠 수 있기 때문에 클래스에 대해서 많이 알고 있어야 하고, 적재적소에 쓸 수 있는 능력을 가지고 있기도 해야 합니다..
(StringBuilder와 StringBuffer의 차이점을 생각하면 좋을 것 같습니다.)
(참고 . 무엇이 Thread Safe한지 아닌지를 확인하는 좋은 방법은 API를 보고 소스를 까면서 (source inspection) 공부해야 합니다. 특히 ThreadLocal, Collection, Map은 많이 봐줘야 한다고 생각드는 군요..)
예를 하나 들어보겠습니다.
아주 대표적인 SimpleDateFormat의 경우를 들도록 하겠습니다.
다행히 SimpleDateFormat의 경우는 동기화에 대한 얘기가 적혀 있습니다.
(http://java.sun.com/javase/6/docs/api/java/text/DateFormat.html)
Date formats are not synchronized. It is recommended to create separate format instances for each thread. If multiple threads access a format concurrently, it must be synchronized externally. |
import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class SimpleDateFormatNotSafety { static SimpleDateFormat df = new SimpleDateFormat("dd-MMM-yyyy"); static String testdata[] = { "01-Jan-1999", "14-Feb-2001", "31-Dec-2007" }; public static void main(String[] args) { Runnable r[] = new Runnable[testdata.length]; for (int i = 0; i < r.length; i++) { final int i2 = i; r[i] = new Runnable() { public void run() { try { for (int j = 0; j < 1000; j++) { String str = testdata[i2]; String str2 = null; Date d = df.parse(str); str2 = df.format(d); if (!str.equals(str2)) { throw new RuntimeException( "date conversion failed after " + j + " iterations. Expected " + str + " but got " + str2); } } } catch (ParseException e) { throw new RuntimeException("parse failed"); } } }; new Thread(r[i]).start(); } } } |
결과는 어떻게 나올까요? Thread unsafety한 결과를 보여줍니다.
Exception in thread "Thread-0" java.lang.RuntimeException: parse failed at SimpleDateFormatNotSafety$1.run(SimpleDateFormatNotSafety.java:31) at java.lang.Thread.run(Unknown Source) Exception in thread "Thread-1" java.lang.RuntimeException: parse failed at SimpleDateFormatNotSafety$1.run(SimpleDateFormatNotSafety.java:31) at java.lang.Thread.run(Unknown Source) Exception in thread "Thread-2" java.lang.RuntimeException: parse failed at SimpleDateFormatNotSafety$1.run(SimpleDateFormatNotSafety.java:31) at java.lang.Thread.run(Unknown Source) |
어떤 분은 이 부분에 대해서 버그 리포트도 올렸습니다.
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4228335
해결방법은 결론은 clone을 써서 리턴하거나, DateFormat를 호출하는 모든 부분에 대해서 Synchronize하라고 되어 있습니다. Thread Unsafety 코드의 특징입니다.
Thread Unsafety 한 SimpleDateFormat 객체를 쓰고 싶어하는 사람을 위해서 Apache Commons-lang에서는 FastDateFormat 이라는 클래스를 추천하고 있습니다.
(http://www.jdocs.com/lang/2.1/org/apache/commons/lang/time/FastDateFormat.html)
어떤 사람은 ThreadLocal을 이용해서 본인만의 Thread-safety한 클래스를 만들어서 사용하기도 합니다. (http://li-ma.blogspot.com/2007/10/thread-safe-date-formatparser.html)
톰캣 7 개발자는 SimpleDateFormat을 계속 사용하기도 합니다. Clone을 이용하는 멋진 테크닉을 구사하여 속도를 향상시키는 법을 잘 알고 있는 것이지요.
private static final SimpleDateFormat FORMAT_PROTOTYPE = new SimpleDateFormat("...", Locale.ROOT); static { format.setTimeZone(GMT_ZONE); } protected SimpleDateFormat format = (SimpleDateFormat)FORMAT_PROTOTYPE.clone(); |
Thread-safety는 양날의 검과 같기 때문에 잘 사용해야 될 것입니다
'java core' 카테고리의 다른 글
String의 intern 메소드에 대해서 잘 쓴 글 (0) | 2010.05.13 |
---|---|
Tomcat6.0.26과 jdk7,jdk6을 사용하지 말 것 (0) | 2010.05.13 |
log4j와 java util logging의 차이 (0) | 2010.04.18 |
Desktop JVM 보안 문제 (0) | 2010.04.17 |
문제 해결 가이드 링크 (0) | 2010.04.15 |