==============转载: 讨论帖 http://www.iteye.com/topic/777716==================
ThreadLocal<T>类在Spring,Hibernate等框架中起到了很大的作用,对于其工作原理,很多网上的文章分析的不够彻底,甚至有些误解。
首先,为了解释ThreadLocal类的工作原理,必须同时介绍与其工作甚密的其他几个类(内部类)
1.ThreadLocalMap
2.Thread
可能有人会觉得Thread与ThreadLocal有什么关系,其实真正的奥秘就在Thread类中的一行:
- ThreadLocal.ThreadLocalMap threadLocals = null ;
ThreadLocal.ThreadLocalMap threadLocals = null;
其中ThreadLocalMap的定义是在ThreadLocal类中,真正的引用却是在Thread类中
那么ThreadLocalMap究竟是什么呢?
可以看到这个类应该是一个Map,JDK的解释是
接下来的重点是ThreadLocalMap中用于存储数据的entry
- static class Entry extends WeakReference<ThreadLocal> {
- /** The value associated with this ThreadLocal. */
- Object value;
- Entry(ThreadLocal k, Object v) {
- super (k);
- value = v;
- }
- }
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
super(k);
value = v;
}
}
从中我们可以发现这个Map的key是ThreadLocal变量,value为用户的值,并不是网上大多数的列子key是线程的名字或者标识
到这里,我们就可以理解ThreadLocal究竟是如何工作的了
1.Thread类中有一个成员变量叫做ThreadLocalMap,它是一个Map,他的Key是ThreadLocal类
2.每个线程拥有自己的申明为ThreadLocal类型的变量,所以这个类的名字叫'ThreadLocal':线程自己的(变量)
3.此变量生命周期是由该线程决定的,开始于第一次初始(get或者set方法)
4.由ThreadLocal的工作原理决定了:每个线程独自拥有一个变量,并非共享或者拷贝
- /**
- * @author mxdba
- *
- */
- public class ThreadLocalSample {
- public static void main(String[] args) {
- ThreadTest test1 = new ThreadTest( 10 );
- ThreadTest test2 = new ThreadTest( 20 );
- test1.start();
- test2.start();
- }
- }
- /**
- * 此线程有两个ThreadLocal变量,但是由于ThreadLocal是延迟初始的,
- * 所以在debug时可以看到线程名为“线程20”的线程的ThreadLocalMap中没有thLcal2这个entry
- * @author mxdba
- *
- */
- class ThreadTest extends Thread {
- public static ThreadLocal<Integer> thLocal = new ThreadLocal<Integer>();
- public static ThreadLocal<String> thLocal2 = new ThreadLocal<String>();
- public Integer num;
- public ThreadTest(Integer num) {
- super ( "线程" + num);
- this .num = num;
- }
- @Override
- public void run() {
- Integer n = thLocal.get();
- if (num != 20 ) {
- String s = thLocal2.get();
- }
- if (n == null ) {
- thLocal.set(num);
- }
- System.out.println(thLocal.get());
- }
- }
/**
* @author mxdba
*
*/
public class ThreadLocalSample {
public static void main(String[] args) {
ThreadTest test1 = new ThreadTest(10);
ThreadTest test2 = new ThreadTest(20);
test1.start();
test2.start();
}
}
/**
* 此线程有两个ThreadLocal变量,但是由于ThreadLocal是延迟初始的,
* 所以在debug时可以看到线程名为“线程20”的线程的ThreadLocalMap中没有thLcal2这个entry
* @author mxdba
*
*/
class ThreadTest extends Thread {
public static ThreadLocal<Integer> thLocal = new ThreadLocal<Integer>();
public static ThreadLocal<String> thLocal2 = new ThreadLocal<String>();
public Integer num;
public ThreadTest(Integer num) {
super("线程" + num);
this.num = num;
}
@Override
public void run() {
Integer n = thLocal.get();
if(num != 20) {
String s = thLocal2.get();
}
if(n == null) {
thLocal.set(num);
}
System.out.println(thLocal.get());
}
}
接下来分析一下源码,就更加清楚了
- /**
- * 关键方法,返回当前Thread的ThreadLocalMap
- * [[[每个Thread返回各自的ThreadLocalMap,所以各个线程中的ThreadLocal均为独立的]]]
- */
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
/** * 关键方法,返回当前Thread的ThreadLocalMap * [[[每个Thread返回各自的ThreadLocalMap,所以各个线程中的ThreadLocal均为独立的]]] */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
- public T get() {
- Thread t = Thread.currentThread();
- /**
- * 得到当前线程的ThreadLocalMap
- */
- ThreadLocalMap map = getMap(t);
- if (map != null) {
- /**
- * 在此线程的ThreadLocalMap中查找key为当前ThreadLocal对象的entry
- */
- ThreadLocalMap.Entry e = map.getEntry(this);
- if (e != null)
- return (T)e.value;
- }
- return setInitialValue();
- }
public T get() { Thread t = Thread.currentThread(); /** * 得到当前线程的ThreadLocalMap */ ThreadLocalMap map = getMap(t); if (map != null) { /** * 在此线程的ThreadLocalMap中查找key为当前ThreadLocal对象的entry */ ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) return (T)e.value; } return setInitialValue(); }
- private T setInitialValue() {
- /**
- * 默认返回null,这个方法为protected可以继承
- */
- T value = initialValue();
- Thread t = Thread.currentThread();
- ThreadLocalMap map = getMap(t);
- if (map != null)
- map.set(this, value);
- else
- /**
- * 初次创建
- */
- createMap(t, value);
- return value;
- }
private T setInitialValue() { /** * 默认返回null,这个方法为protected可以继承 */ T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else /** * 初次创建 */ createMap(t, value); return value; }
- /**
- * 给当前thread初始ThreadlocalMap
- */
- void createMap(Thread t, T firstValue) {
- t.threadLocals = new ThreadLocalMap( this , firstValue);
- }
/**
* 给当前thread初始ThreadlocalMap
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
通过上边的分析,我们发现,ThreadLocal类的使用虽然是用来解决多线程的问题的,但是还是有很明显的针对性
1.最明显的,ThreadLoacl变量的活动范围为某线程,并且我的理解是该线程“专有的,独自霸占”,对该变量的所有操作均有该线程完成! 也就是说,ThreadLocal不是用来解决共享,竞争问题的。典型的应用莫过于Spring,Hibernate等框架中对于多线程的处理了。
下面来看一个 hibernate 中典型的 ThreadLocal 的应用:
- private static final ThreadLocal threadSession = new ThreadLocal();
- public static Session getSession() throws InfrastructureException {
- Session s = (Session) threadSession.get();
- try {
- if (s == null ) {
- s = getSessionFactory().openSession();
- threadSession.set(s);
- }
- } catch (HibernateException ex) {
- throw new InfrastructureException(ex);
- }
- return s;
- }
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
这段代码,每个线程有自己的ThreadLocalMap,每个ThreadLocalMap中根据需要初始加载threadSession,这样的好处就是介于singleton与prototype之间,应用singleton无法解决线程,应用prototype开销又太大,有了ThreadLocal之后就好了,对于需要线程“霸占”的变量用ThreadLocal,而该类实例的方法均可以共享。
2.关于内存泄漏:
虽然ThreadLocalMap已经使用了weakReference,但是还是建议能够显示的使用remove方法。
==============转载: 讨论帖 http://ari.iteye.com/blog/757478==================
一、ThreadLocal概述
学习JDK中的类,首先看下JDK API对此类的描述,描述如下:
API表达了下面几种观点:
1、ThreadLocal不是线程,是线程的一个变量,你可以先简单理解为线程类的属性变量。
2、ThreadLocal 在类中通常定义为静态类变量。
3、每个线程有自己的一个ThreadLocal,它是变量的一个‘拷贝’,修改它不影响其他线程。
既然定义为类变量,为何为每个线程维护一个副本(姑且成为‘拷贝’容易理解),让每个线程独立访问?多线程编程的经验告诉我们,对于线程共享资源(你可以理解为属性),资源是否被所有线程共享,也就是说这个资源被一个线程修改是否影响另一个线程的运行,如果影响我们需要使用synchronized同步,让线程顺序访问。
ThreadLocal适用于资源共享但不需要维护状态的情况,也就是一个线程对资源的修改,不影响另一个线程的运行;这种设计是‘空间换时间’ ,synchronized顺序执行是‘时间换取空间’ 。
二、ThreadLocal方法介绍
T | get () 返回此线程局部变量的当前线程副本中的值。 |
protected T | initialValue () 返回此线程局部变量的当前线程的“初始值”。 |
void | remove () 移除此线程局部变量当前线程的值。 |
void | set (T value) 将此线程局部变量的当前线程副本中的值设置为指定值。 |
三、深入源码
ThreadLocal有一个ThreadLocalMap静态内部类,你可以简单理解为一个MAP,这个‘Map’为每个线程复制一个变量的‘拷贝’存储其中。
当线程调用ThreadLocal.get()方法获取变量时,首先获取当前线程引用,以此为key去获取响应的ThreadLocalMap,如果此‘Map’不存在则初始化一个,否则返回其中的变量,代码如下:
- 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();
- }
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;
- }
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);
- }
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;
- }
/**
* 获取线程的ThreadLocalMap 属性实例
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
上面我们知道变量副本存放于何处,这里我们简单说下如何被java的垃圾收集机制收集,当我们不在使用是调用set(null),此时不在将引用指向该‘map’,而线程退出时会执行资源回收操作,将申请的资源进行回收,其实就是将属性的引用设置为null。这时已经不在有任何引用指向该map,故而会被垃圾收集。
四、ThreadLocal应用示例
在我的另一篇文章,对ThreadLocal的使用做了一个实例,此示例也可以用作生产环境,请参见:http://ari.iteye.com/blog/757641