首页 > 代码库 > 浅谈对Java中ThreadLocal类的理解
浅谈对Java中ThreadLocal类的理解
首先要明确:ThreadLocal不是一个多线程类,或者应该叫做线程局部变量。这从ThreadLocal的JDK定义中就可以看到
public class ThreadLocal<T>extends Object
可以看出ThreadLocal只是一个普普通通的类,并没有继承自Thread或实现Runnable接口。
同时也可以看到ThreadLocal使用了泛型,这样他就可以操作几乎任何类型的数据了。下面说JDK API代码时具体再说。
对此类,看看JDK API中的部分描述:
该类提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
1、ThreadLocal不是线程,而是县城的一个局部变量
2、再使用时ThreadLocal通常作为类中的一个private static变量使用
3、每个线程都有一个自己的局部变量,之间互不冲突,对变量值的操作互不影响,不存在资源冲突。
总体来说,ThreadLocal作为线程的工具,为每一个线程独立的提供了一个存储与访问线程变量的方式,使得不同线程间存储的数据互不影响(因此不会出现资源竞争问题)。
下面就说说ThreadLocal是如何作为一个线程工具,为每个线程提供存储与访问线程局部变量的。
ThreadLocal提供了以下几个API用于存取线程局部变量:
T | get() 返回此线程局部变量的当前线程副本中的值。 |
protected T | initialValue() 返回此线程局部变量的当前线程的初始值。 |
void | remove() 移除此线程局部变量的值。 |
void | set(T value) 将此线程局部变量的当前线程副本中的值设置为指定值。 |
可以看到每个都是泛型类型的。接口的工作原理下面会具体介绍。
ThreadLocal类中还提供了一个静态的内部类:
static class ThreadLocalMap
我们可以把这个ThreadLocalMap看做是一个普通的MAP类型,可以保存一个键值对即可,这样便于理解。
这个ThreadLocalMap在ThreadLocal中定义,但却在线程类java.lang.Thread中使用。在Thread类中有以下定义:
ThreadLocal.ThreadLocalMap threadLocals = null
言外之意就是Thread类中有一个类似于Map的ThreadLocal.ThreadLocalMap 类型的变量,可以用于存储键值对类型的值。
下面上一个简单的例子,根据输出结果,分析Thread与ThreadLocal工作过程。
类SerialNum
public class SerialNum { // The next serial number to be assigned private static int nextSerialNum = 0; public static ThreadLocal serialNum = new ThreadLocal() { protected synchronized Object initialValue() { return new Integer(nextSerialNum++); } }; public static int get() { return ((Integer) (serialNum.get())).intValue(); } public static void set(int val){ serialNum.set(val); } }
类SerialNum中定义了一个private static ThreadLocal 类型的匿名内部类serialNum变量,其实完全可以直接new ThreadLocal,这样写只是为了实现重写其initialValue方法,因为在ThreadLocal中initialValue()方法返回一个NULL值,需要说明的是,initialValue()方法是一个懒加载方法,被ThreadLocal类内部调用,并且只在第一次threadLocal类的get()被调用时调用,重写它是为了让局部变量有一个初始值。
SerialNum类的静态get()和set()方法都调用ThreadLocal类的get()和set()方法。
类:ThreadTest
public class ThreadTest extends Thread{ @Override public void run() { // TODO Auto-generated method stub //先打印初始值 System.out.println(Thread.currentThread().getName()+":"+SerialNum.get()); //当前值加4 SerialNum.set(SerialNum.get()+4); //打印加4后的值 System.out.println(Thread.currentThread().getName()+":"+SerialNum.get()); } public static void main(String[] args) { // TODO Auto-generated method stub ThreadTest t = new ThreadTest(); ThreadTest t2 = new ThreadTest(); ThreadTest t3 = new ThreadTest(); t.start(); t2.start(); t3.start(); } }
ThreadTest中的run方法中调用了SerialNum类的静态get()和set()方法。
输出结果如下:
Thread-2:2
Thread-0:0
Thread-1:1
Thread-2:6
Thread-0:4
Thread-1:5
下面我们开始分析为什么会出现这样的结果。我们就拿线程Thread-0来说,它对应的就是t.start()启动的线程
1、当t线程先运行时(thread-0),会调用SerialNum.get(),而其会再调用threadLocal的get方法。ThreadLocal的get()方法如下:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) return (T)map.get(this); // Maps are constructed lazily. if the map for this thread // doesn't exist, create it, with this ThreadLocal and its // initial value as its only entry. T value = http://www.mamicode.com/initialValue();>get()方法中先获得当前线程,然后调用getMap(),API方法如下:
ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
getMap()返回当前线程中的 threadLocals变量,前面我们说过,Thread中定义了一个ThreadLocal.ThreadLocalMap类型的变量,就是这个了。
然后在get()方法中,检查map为不为NUll,如果是NULL值,所以会调用initialValue()。而这个方法被我们重写了,它返回了SerialNum类中定义的静态变量nextSerialNum当前值,然后将nextSerialNum+1。因为nextSerialNum初始为0,所以返回的是0。然后调用createMap(t,value)。这个API内容如下:
void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }可以看到 createMap中创建了一个ThreadLoadMap对象,并复制给当前线程的threadLocals变量。新创建的ThreadLocalMap类中保存了局部变量的值value,其中键为this,即我们在SerialNum类中定义的那个private static ThreadLocal serialNum。
最后get()方法返回初始化好的value值0。
同时
以上就是thead-0第一次输出打印出0的原因。
2、当线程thread-0运行到SerialNum.set(SerialNum.get()+4);这一句时,我们看发生了什么
首先SerialNum.get()+4会取出threadLocal变量serialNum中保存的线程局部变量值,回头看看ThreadLocal.get()方法的介绍,因为此时当前线程中已经有了ThreadLocalMap 类型的threadLocals,所以会直接调用map.get(this);其中this依然是我们在SerialNum类中定义的那个private static ThreadLocal serialNum。
这样就返回了map中的值。然后+4后调用ThreadLocal的set()方法。我们看看set()方法的JDK内容:
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }可以看到set()方法首先也是得到当前线程,从当前线程中获取threadLocalMap类型的map。如果map不为null,就将value保存进去。如果为null,就在createMap中new一个ThreadLocalMap,保存value,然后将这个 新new的threadLocalMap类型的对象赋值给当前线程的threadLocals变量(详见createMap()方法)。
经过运行后,局部变量值就等于0+4=4了。这也是为什么第二次打印出的值为thread-0:4。
再简单说说第二个线程thread-1。第二个线程启动时,因为静态变量nextSerialNum已经为1(被线程thread-0读取后++)。所以他是从1开始打印。并且后面的+4操作与线程thread-0和thread-2并不冲突。因为:
thread-0这个线程的局部变量被保存进threadLocalMap后,这个threadLocalMap变量被复制给这个线程(也就是thread-0自己)的threadLocals变量了。同样的,thread-1;thread-2这两个线程的局部变量保存进threadLocalMap后,都分别复制给自己这个线程的threadLocals变量了。所以以后再操作都是从自己线程里面取threadLocalMap,当然互不影响。相同的一点是这三个线程的threadLocalMap中的KEY都为同一个ThreadLocal对象。
通过这个例子还能看出一点,局部变量初始化后,就再和开始那个nextSerialNum没关系了,这应该就是JDK API中描述的:访问一个变量(通过其get 或 set 方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本
以上就是自己对ThreadLocal使用的浅谈。开始怕说不清楚,就想写的直白点。后来自己都感觉啰嗦了。
浅谈对Java中ThreadLocal类的理解