虽然ThreadLocal与并发问题相关,但是许多程序员仅仅将它作为一种用于“方便传参”的工具,胖哥认为这也许并不是ThreadLocal设计的目的,它本身是为线程安全和某些特定场景的问题而设计的。
ThreadLocal是什么呢!
每个ThreadLocal可以放一个线程级别的变量,但是它本身可以被多个线程共享使用,而且又可以达到线程安全的目的,且绝对线程安全。
例如:
public final static ThreadLocal RESOURCE = new ThreadLocal();
RESOURCE代表一个可以存放String类型的ThreadLocal对象,此时任何一个线程可以并发访问这个变量,对它进行写入、读取操作,都是线程安全的。比如一个线程通过RESOURCE.set(“aaaa”);将数据写入ThreadLocal中,在任何一个地方,都可以通过RESOURCE.get();将值获取出来。
public class ThreadLocalTest {
static class ResourceClass {
public final static ThreadLocal RESOURCE_1 =
new ThreadLocal();
public final static ThreadLocal RESOURCE_2 =
new ThreadLocal();
}
static class A {
public void setOne(String value) {
ResourceClass.RESOURCE_1.set(value);
}
public void setTwo(String value) {
ResourceClass.RESOURCE_2.set(value);
}
}
static class B {
public void display() {
System.out.println(ResourceClass.RESOURCE_1.get()
+ ":" + ResourceClass.RESOURCE_2.get());
}
}
public static void main(String []args) {
final A a = new A();
final B b = new B();
for(int i = 0 ; i < 15 ; i ++) {
final String resouce1 = "线程-" + I;
final String resouce2 = " value = (" + i + ")";
new Thread() {
public void run() {
try {
a.setOne(resouce1);
a.setTwo(resouce2);
b.display();
}finally {
ResourceClass.RESOURCE_1.remove();
ResourceClass.RESOURCE_2.remove();
}
}
}.start();
}
}
}
关于这段代码,我们先说几点。
◎ 定义了两个ThreadLocal变量,最终的目的就是要看最后两个值是否能对应上,这样才有机会证明ThreadLocal所保存的数据可能是线程私有的。
◎ 使用两个内部类只是为了使测试简单,方便大家直观理解,大家也可以将这个例子的代码拆分到多个类中,得到的结果是相同的。
◎ 测试代码更像是为了方便传递参数,因为它确实传递参数很方便,但这仅仅是为了测试。
◎ 在finally里面有remove()操作,是为了清空数据而使用的。为何要清空数据,在后文中会继续介绍细节。
测试结果如下:
线程-6: value = (6)
线程-9: value = (9)
线程-0: value = (0)
线程-10: value = (10)
线程-12: value = (12)
线程-14: value = (14)
线程-11: value = (11)
线程-3: value = (3)
线程-5: value = (5)
线程-13: value = (13)
线程-2: value = (2)
线程-4: value = (4)
线程-8: value = (8)
线程-7: value = (7)
线程-1: value = (1)
大家可以看到输出的线程顺序并非最初定义线程的顺序,理论上可以说明多线程应当是并发执行的,但是依然可以保持每个线程里面的值是对应的,说明这些值已经达到了线程私有的目的。
不是说共享变量无法做到线程私有吗?它又是如何做到线程私有的呢?这就需要我们知道一点点原理上的东西,否则用起来也没那么放心,请看下面的介绍。
图5-6 getMap(Thread)操作
在这里,我们看到ThreadLocalMap其实就是线程里面的一个属性,它在Thread类中的定义是:
ThreadLocal.ThreadLocalMap threadLocals = null;
这种方法很容易让人混淆,因为这个ThreadLocalMap是ThreadLocal里面的内部类,放在了Thread类里面作为一个属性而存在,ThreadLocal本身成为这个Map里面存放的Key,用户输入的值是Value。太乱了,理不清楚了,画个图来看看(见图5-7)。
简单来讲,就是这个Map对象在Thread里面作为私有的变量而存在,所以是线程安全的。ThreadLocal通过Thread.currentThread()获取当前的线程就能得到这个Map对象,同时将自身作为Key发起写入和读取,由于将自身作为Key,所以一个ThreadLocal对象就能存放一个线程中对应的Java对象,通过get也自然能找到这个对象。
图5-7 Thread与ThreadLocal的伪代码关联关系
如果还没有理解,则可以将思维放宽一点。当定义变量String a时,这个“a”其实只是一个名称(在第3章中已经说到了常量池),虚拟机需要通过符号表来找到相应的信息,而这种方式正好就像一种K-V结构,底层的处理方式也确实很接近这样,这里的处理方式是显式地使用Map来存放数据,这也是一种实现手段的变通。
现在有了思路,继续回到上面的话题,为了验证前面的推断和理解,来看看createMap方法的细节,如图5-8所示。
图5-8 createMap操作
这段代码是执行一个创建新的Map的操作,并且将第一个值作为这个Map的初始化值,由于这个Map是线程私有的,不可能有另一个线程同时也在对它做put操作,因此这里的赋值和初始化是绝对线程安全的,也同时保证了每一个外部写入的值都将写入到Map对象中。
最后来看看get()、remove()代码,或许看到这里就可以认定我们的理论是正确的,如图5-9所示。
图5-9 get()/remove()方法的代码片段
给我们的感觉是,这样实现是一种技巧,而不是一种技术。
其实是技巧还是技术完全是从某种角度来看的,或者说是从某种抽象层次来看的,如果这段代码在C++中实现,难道就叫技术,不是技巧了吗?当然不是!胖哥认为技术依然是建立在思想和方法基础上的,只是看实现的抽象层次在什么级别。就像在本书中多个地方探讨的一些基础原理一样,我们探讨了它的思想,其实它的实现也是基于某种技巧和手段的,只是对程序封装后就变成了某种语法和API,因此胖哥认为,一旦学会使用技巧思考问题,就学会了通过技巧去看待技术本身。我们应当通过这种设计,学会一种变通和发散的思维,学会理解各种各样的场景,这样便可以积累许多真正的财富,这些财富不是通过某些工具的使用或测试就可以获得的。
ThreadLocal的这种设计很完美吗?
不是很完美,它依然有许多坑,在这里对它容易误导程序员当成传参工具就不再多提了,下面我们来看看它的使用不当会导致什么技术上的问题。
#####################################个人总结 ####################################
Thread.java源码中:
ThreadLocal.ThreadLocalMap threadLocals = null;
即:每个Thread对象都有一个ThreadLocal.ThreadLocalMap成员变量,ThreadLocal.ThreadLocalMap是一个ThreadLocal类的静态内部类(如下所示),所以Thread类可以进行引用.
static class ThreadLocalMap {
所以每个线程都会有一个ThreadLocal.ThreadLocalMap对象的引用
当在ThreadLocal中进行设值的时候:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
首先获取当前线程的引用,然后获取当前线程的ThreadLocal.ThreadLocalMap对象(t.threadLocals变量就是ThreadLocal.ThreadLocalMap的变量),如果该对象为空就创建一个,如下所示:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
这个this变量就是ThreadLocal的引用,对于同一个ThreadLocal对象每个线程都是相同的,但是每个线程各自有一个ThreadLocal.ThreadLocalMap对象保存着各自ThreadLocal引用为key的值,所以互不影响,而且:如果你新建一个ThreadLocal的对象,这个对象还是保存在每个线程同一个ThreadLocal.ThreadLocalMap对象之中,因为一个线程只有一个ThreadLocal.ThreadLocalMap对象,这个对象是在第一个ThreadLocal第一次设值的时候进行创建,如上所述的createMap方法.
ThreadLocalMap(ThreadLocal firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
总结:
深入研究java.lang.ThreadLocal类:http://blog.csdn.net/xiaohulunb/article/details/19603611
API说明:
ThreadLocal(),T get(),protected T initialValue(),void remove(),void set(T value)
典型实例:
1.Hiberante的Session 工具类HibernateUtil
2.通过不同的线程对象设置Bean属性,保证各个线程Bean对象的独立性。
ThreadLocal使用的一般步骤:
1、在多线程的类(如ThreadDemo类)中,创建一个ThreadLocal对象threadXxx,用来保存线程间需要隔离处理的对象xxx。
2、在ThreadDemo类中,创建一个获取要隔离访问的数据的方法getXxx(),在方法中判断,若ThreadLocal对象为null时候,应该new()一个隔离访问类型的对象,并强制转换为要应用的类型。
3、在ThreadDemo类的run()方法中,通过getXxx()方法获取要操作的数据,这样可以保证每个线程对应一个数据对象,在任何时刻都操作的是这个对象。
与Synchonized的对比:
ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
一句话理解ThreadLocal:向ThreadLocal里面存东西就是向它里面的Map存东西的,然后ThreadLocal把这个Map挂到当前的线程底下,这样Map就只属于这个线程了。
使用ThreadLocal改进你的层次的划分(spring事务的实现):http://blog.csdn.net/zhouyong0/article/details/7761835
源码剖析之ThreadLocal:http://wangxinchun.iteye.com/blog/1884228
Java中的ThreadLocal源码解析(上):http://maosidiaoxian.iteye.com/blog/1939142
ThreadLocal与synchronized:http://blog.csdn.net/yangairong1984/article/details/2294572
Java线程:深入ThreadLocal:http://lavasoft.blog.51cto.com/62575/258459(一个ThreadLocal的模拟实现)
Java多线程(六)、ThreadLocal类:http://blog.csdn.net/lonelyroamer/article/details/7998137