浅谈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

你可能感兴趣的:(threadLocal)