ThreadLocal是一个提供线程本地变量(线程局部变量 / thread-local variables)的类。每一个通过set或get方法访问ThreadLocal变量的线程,都会生成独立的,只属于这个线程变量的副本(“every thread that accesses a ThreadLocal variable via its get or set method has its own, independently initialized copy of the variable”)
简单来讲,ThreadLocal为线程一个变量的副本,而此副本不会和其它线程的变量副本冲突。
只要线程是活动的并且 ThreadLocal 实例是可访问的,每个线程都保持对其线程本地变量副本的隐式引用;在线程消失之后,其线程本地实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。
线程本地变量是static和final的。
1 public class ThreadLocalDateUtil implements IDateUtil { 2 3 private static final ThreadLocal<SimpleDateFormat> sdf = new ThreadLocal<SimpleDateFormat>(){ 4 5 @Override 6 public SimpleDateFormat get(){ 7 return super.get(); 8 } 9 10 /** 11 * Returns the current thread's "initial value" for this thread-local variable. 12 * 如果不覆盖initialValue,第一次get返回null 13 */ 14 @Override 15 public SimpleDateFormat initialValue(){ 16 return new SimpleDateFormat("yyyy MM dd"); 17 } 18 19 @Override 20 public void set(SimpleDateFormat value){ 21 super.set(value); 22 } 23 24 /** 25 * 移除此线程局部变量的值。这可能有助于减少线程局部变量的存储需求。 26 * 如果再次访问此线程局部变量,那么在默认情况下它将拥有其 initialValue。 27 */ 28 @Override 29 public void remove(){ 30 super.remove(); 31 } 32 }; 33 34 public String convertString2Date(Date date) throws ParseException{ 35 return sdf.get().format(date); 36 } 37 }
ThreadLocal方法不多,其中get(),set()和remove()无需过多描述。initialValue()无需现实调用,一般都重写initialValue方法,以给定一个特定的初始值。
利用ThreadLocal解决SimpleDateFormat线程安全的问题(3.ThreadLocalDateUtil),并比较ThreadLocal方法和Synchronized方法。
以下是使用Synchronized的方法:
1 /** 2 * 使用同步机制解决SimpleDateFormat线程安全问题 3 * @author ccycyang 4 * 5 */ 6 public class SynDateUtil implements IDateUtil { 7 8 private SimpleDateFormat sdf = new SimpleDateFormat("yyyy MM dd"); 9 10 public String convertString2Date(Date date) throws ParseException{ 11 String result; 12 synchronized (sdf) { 13 result = sdf.format(date); 14 } 15 return result; 16 } 17 }
一下是测试类,使用了CyclicBarrier来控制线程:
1 /** 2 * Refer to JCIP Chapter 12 3 * @author ccycyang 4 * 5 */ 6 public class TestSimpleDateFormat { 7 8 private static final ExecutorService pool = Executors.newCachedThreadPool(); 9 private final CyclicBarrier barrier; //Make sure all the threads is ready 10 private final BarrierTimer timer; //Calculate the time 11 private int nParis; //thread number 12 13 public TestSimpleDateFormat(int nParis) { 14 // TODO Auto-generated constructor stub 15 this.nParis = nParis; 16 this.timer = new BarrierTimer(); 17 this.barrier=new CyclicBarrier(nParis+1); 18 } 19 20 /** 21 * @param args 22 * @throws InterruptedException 23 */ 24 public static void main(String[] args) throws InterruptedException { 25 26 new TestSimpleDateFormat(5000).test(false); 27 28 pool.shutdown(); 29 } 30 31 private void test(boolean isSyn){ 32 try{ 33 timer.clear(); 34 for(int i=0;i<nParis;i++){ 35 pool.execute(timer); 36 if(isSyn){ 37 pool.execute(new AccessDateThread(new SynDateUtil())); 38 }else{ 39 pool.execute(new AccessDateThread(new ThreadLocalDateUtil())); 40 } 41 } 42 barrier.await(); //wait for all thread's ready. 43 barrier.await(); //wait for all thread's finished 44 45 long nsPerTrai = timer.getTime() / nParis; 46 System.out.println("Throughput: "+nsPerTrai); 47 }catch(Exception e){ 48 throw new RuntimeException(e); 49 } 50 51 } 52 53 class AccessDateThread implements Runnable { 54 private IDateUtil dateUtil; 55 56 public AccessDateThread(IDateUtil du){ 57 this.dateUtil = du; 58 } 59 @Override 60 public void run() { 61 // TODO Auto-generated method stub 62 try { 63 64 barrier.await(); 65 66 dateUtil.convertString2Date(new Date()); 67 68 barrier.await(); 69 } catch (Exception e) { 70 // TODO Auto-generated catch block 71 e.printStackTrace(); 72 } 73 } 74 75 } 76 77 //JCIP Chapter 12 78 class BarrierTimer implements Runnable{ 79 private boolean started; 80 private long startTime,endTime; 81 82 public synchronized void run(){ 83 long t=System.nanoTime(); 84 if(!started){ 85 started=true; 86 startTime = t; 87 }else{ 88 endTime = t; 89 } 90 } 91 92 public synchronized void clear(){ 93 started = false; 94 } 95 public synchronized long getTime(){ 96 return endTime - startTime; 97 } 98 } 99 }
测试结果显示ThreadLocal确实比Synchronized有提升,时间会短些
apache commons-lang包的DateFormatUtils或者FastDateFormat实现,apache保证是线程安全的,并且更高效。
有时间要看看commons-lang包里的FormatUtils怎么实现。
Update: ThreadLocal有可能会导致Memory Leak:
http://javarevisited.blogspot.co.at/2013/01/threadlocal-memory-leak-in-java-web.html
参考:
JDK: http://docs.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html
Java Best Practices: http://www.javacodegeeks.com/2010/07/java-best-practices-dateformat-in.html