浅谈ThreadLocal

1. 什么是ThreadLocal

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的。

 

2. ThreadLocal有什么用

  • ThreadLocal是现实线程安全(thread-safe)的一种方式。通过为每一个线程都提供了一份变量,线程同时访问变量而互不影响,典型的“空间换时间”。
  • ThreadLocal提供一种方式,可以使其状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

 

3. ThreadLocal类

 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方法,以给定一个特定的初始值。

 

4. ThreadLocal实例

利用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

posted @ 2013-03-12 16:08  macemers  阅读(449)  评论(0编辑  收藏  举报