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

  • 相关阅读:
    利用TLE数据确定卫星轨道(1)-卫星轨道和TLE
    关于javascript的单线程和异步的一些问题
    javascript编译与运行机理(1)--
    springMVC解决跨域
    如何实现免登陆功能(cookie session?)
    Map 接口有哪些类
    平时使用了哪些线程池
    Maven install报错:MojoFailureException ,To see the full stack trace of the errors, re-run Maven with the -e switch.解决
    记录新建dorado项目更新规则中报错
    Dynamic Web Module 3.0 requires Java 1.6 or newer
  • 原文地址:https://www.cnblogs.com/techyc/p/2955569.html
Copyright © 2020-2023  润新知