ThreadLocal

来源:互联网 发布:centos安装ant 编辑:程序博客网 时间:2024/05/01 00:51

一、ThreadLocal概述

       学习JDK中的类,首先看下JDK API对此类的描述,描述如下:

JDK API 写道
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。

    API表达了下面几种观点:

1、ThreadLocal不是线程,是线程的一个变量,你可以先简单理解为线程类的属性变量。

2、ThreadLocal 在类中通常定义为静态类变量。

3、每个线程有自己的一个ThreadLocal,它是变量的一个‘拷贝’,修改它不影响其他线程。

 

    既然定义为类变量,为何为每个线程维护一个副本(姑且成为‘拷贝’容易理解),让每个线程独立访问?多线程编程的经验告诉我们,对于线程共享资源(你可以理解为属性),资源是否被所有线程共享,也就是说这个资源被一个线程修改是否影响另一个线程的运行,如果影响我们需要使用synchronized同步,让线程顺序访问。

 

   ThreadLocal适用于资源共享但不需要维护状态的情况,也就是一个线程对资源的修改,不影响另一个线程的运行;这种设计是‘空间换时间’,synchronized顺序执行是‘时间换取空间’

 

二、ThreadLocal方法介绍

 

 

 Tget()
          返回此线程局部变量的当前线程副本中的值。protected  TinitialValue()
          返回此线程局部变量的当前线程的“初始值”。 voidremove()
          移除此线程局部变量当前线程的值。 voidset(T value)
          将此线程局部变量的当前线程副本中的值设置为指定值。

 

三、深入源码

    ThreadLocal有一个ThreadLocalMap静态内部类,你可以简单理解为一个MAP,这个‘Map’为每个线程复制一个变量的‘拷贝’存储其中。

    当线程调用ThreadLocal.get()方法获取变量时,首先获取当前线程引用,以此为key去获取响应的ThreadLocalMap,如果此‘Map’不存在则初始化一个,否则返回其中的变量,代码如下:

    

Get方法代码 
 public T get() {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null) {            ThreadLocalMap.Entry e = map.getEntry(this);            if (e != null)                return (T)e.value;        }        return setInitialValue(); }


    调用get方法如果此Map不存在首先初始化,创建此map,将线程为key,初始化的vlaue存入其中,注意此处的initialValue,我们可以覆盖此方法,在首次调用时初始化一个适当的值。setInitialValue代码如下:

    private T setInitialValue() {        T value = initialValue();        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);        return value;    }

    set方法相对比较简单如果理解以上俩个方法,获取当前线程的引用,从map中获取该线程对应的map,如果map存在更新缓存值,否则创建并存储,代码如下:

    public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }

    对于ThreadLocal在何处存储变量副本,我们看getMap方法:获取的是当前线程的ThreadLocal类型的threadLocals属性。显然变量副本存储在每一个线程中。

/** * 获取线程的ThreadLocalMap 属性实例 */ThreadLocalMap getMap(Thread t) {        return t.threadLocals;  }

    上面我们知道变量副本存放于何处,这里我们简单说下如何被java的垃圾收集机制收集,当我们不在使用是调用set(null),此时不在将引用指向该‘map’,而线程退出时会执行资源回收操作,将申请的资源进行回收,其实就是将属性的引用设置为null。这时已经不在有任何引用指向该map,故而会被垃圾收集。


 四、ThreadLocal应用示例


引用Tim Cull的博文“SimpleDateFormat: Performance Pig”介绍下ThreadLocal的简单使用,同时也对SimpleDateFormat的使用有个深入的了解。

Tim Cull 写道
Just yesterday I came across this problem “in the wild” for the third time in my career so far: an application with performance problems creating tons of java.text.SimpleDateFormat instances. So, I have to get this out there: creating a new instance of SimpleDateFormat is incredibly expensive and should be minimized. In the case that prompted this post, I was using JProfiler to profile this code that parses a CSV file and discovered that 50% of the time it took to suck in the file and make 55,000 objects out of it was spent solely in the constructor of SimpleDateFormat. It created and then threw away a new one every time it had to parse a date. Whew!

“Great,” you think, “I’ll just create one, static instance, slap it in a field in a DateUtils helper class and life will be good.”

Well, more precisely, life will be good about 97% of the time. A few days after you roll that code into production you’ll discover the second cool fact that’s good to know: SimpleDateFormat is not thread safe. Your code will work just fine most of the time and all of your regression tests will probably pass, but once your system gets under a production load you’ll see the occasional exception.

“Fine,” you think, “I’ll just slap a ’synchronized’ around my use of that one, static instance.”

Ok, fine, you could do that and you’d be more or less ok, but the problem is that you’ve now taken a very common operation (date formatting and parsing) and crammed all of your otherwise-lovely, super-parallel application through a single pipe to get it done.

      

     大致意思:Tim Cull碰到一个SimpleDateFormat带来的严重的性能问题,该问题主要有SimpleDateFormat引发,创建一个SimpleDateFormat实例的开销比较昂贵,解析字符串时间时频繁创建生命周期短暂的实例导致性能低下。即使将SimpleDateFormat定义为静态类变量,貌似能解决这个问题,但是SimpleDateFormat是非线程安全的,同样存在问题,如果用‘synchronized’线程同步同样面临问题,同步导致性能下降(线程之间序列化的获取SimpleDateFormat实例)。

    Tim Cull使用Threadlocal解决了此问题,对于每个线程SimpleDateFormat不存在影响他们之间协作的状态,为每个线程创建一个SimpleDateFormat变量的拷贝或者叫做副本,代码如下:

import java.text.DateFormat;import java.text.ParseException;import java.text.SimpleDateFormat;import java.util.Date;/** * 使用ThreadLocal以空间换时间解决SimpleDateFormat线程安全问题。 * @author  * */public class DateUtil {private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";@SuppressWarnings("rawtypes")private static ThreadLocal threadLocal = new ThreadLocal() {protected synchronized Object initialValue() {return new SimpleDateFormat(DATE_FORMAT);}};public static DateFormat getDateFormat() {return (DateFormat) threadLocal.get();}public static Date parse(String textDate) throws ParseException {return getDateFormat().parse(textDate);}}

   创建一个ThreadLocal类变量,这里创建时用了一个匿名类,覆盖了initialValue方法,主要作用是创建时初始化实例。也可以采用下面方式创建;


//第一次调用get将返回nullprivate static ThreadLocal threadLocal = new ThreadLocal();//获取线程的变量副本,如果不覆盖initialValue,第一次get返回null,故需要初始化一个SimpleDateFormat,并set到threadLocal中public static DateFormat getDateFormat() {DateFormat df = (DateFormat) threadLocal.get();if(df==null){df = new SimpleDateFormat(DATE_FORMAT)threadLocal.set(df);}return df;}

   我们看下我们覆盖的initialValue方法:

protected T initialValue() {        return null;//直接返回null    }




0 0
原创粉丝点击