ThreadLocal感觉并不是很常用,但是在使用kryo进行序列化时出现了这个东西,说kryo序列化是非线程安全的,可以使用ThreadLocal来达到线程安全。
ThreadLocal在JDK 1.2中就已经存在了,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。
使用这个工具类可以很方便的使用多线程来进行参数传递,并且线程间的数据是隔离,不同线程之间的数据不会相互干扰.。
定义一个全局的ThreadLocal,这个ThreadLocal能够放多个线程级别的变量,可是它又能够被多个线程共享使用,并且又能够达到线程安全的目的,且绝对线程安全。
我们先来看一个简单的ThreadLocal使用例子:
1 public classThreadLocalTest {2
3 private static ThreadLocal threadLocalA = new ThreadLocal();4 private static ThreadLocal threadLocalB = new ThreadLocal();5
6 public static voidmain(String[] args) {7
8 for (int i = 0; i < 2; i++) {9 final int iData =i;10 Thread thread = new Thread(newRunnable() {11 public voidrun() {12 threadLocalA.set("A" +iData);13 threadLocalB.set("B" +iData);14 newTestA().get();15 newTestB().get();16 }17 });18
19 thread.start();20 }21 }22
23 static classTestA {24 public voidget() {25 System.out.println("TestA-threadLocalA:" + Thread.currentThread().getName() + "=" +threadLocalA.get());26 System.out.println("TestA-threadLocalB:" + Thread.currentThread().getName() + "=" +threadLocalB.get());27 }28 }29
30 static classTestB {31 public voidget() {32 System.out.println("TestB-threadLocalA" + Thread.currentThread().getName() + "=" +threadLocalA.get());33 System.out.println("TestB-threadLocalB" + Thread.currentThread().getName() + "=" +threadLocalB.get());34 }35 }36 }
运行以上代码,输出的结果可能(在运行一次,结果可能不是这样子的)如下:
TestA-threadLocalA:Thread-0=A0
TestA-threadLocalB:Thread-0=B0
TestB-threadLocalAThread-0=A0
TestB-threadLocalBThread-0=B0
TestA-threadLocalA:Thread-1=A1
TestA-threadLocalB:Thread-1=B1
TestB-threadLocalAThread-1=A1
TestB-threadLocalBThread-1=B1
以上可以看出,一个ThreadLocal 可以存储多个线程的变量,但是获取的数据不会发生错乱。
每个线程都有一个自己的ThreadLocal.ThreadLocalMap对象
ThreadLocalMap是ThreadLocal 的一个内部类,数据的存储就是通过他来实现的。
要搞清工作原理,就要分析一下源码, 查看ThreadLocal源码,它是一个泛型的类,类中主要的三个public方法
set(T value) 设置当前线程的线程局部变量的值。
T get()返回当前线程所对应的线程局部变量。
void remove()将当前线程局部变量的值删除
set(T value)方法的实现
执行大概分以下三步:
1、获取取得当前的线程。
2、获取线程里面ThreadLocal.ThreadLocalMap的对象
3、看这个ThreadLocal.ThreadLocalMap是否存在,不存在创建一个ThreadLocal.ThreadLocalMap,并且把它赋值给线程的 threadLocals 变量。
创建的过程代码如下:
创建的过程步骤如下:
生成一个 Entry的数组,Entry是一个ThreadLocal弱引用,并且增加了一个value的,我们get/set的值就保存在这个变量里。
static class Entry extends WeakReference>{/**The value associated with this ThreadLocal.*/Object value;
Entry(ThreadLocal>k, Object v) {super(k);
value=v;
}
}
这里要说明一下:
ThreadLocal.ThreadLocalMap规定了table的大小必须是2的N次幂,主要是为了进行取模运算,因为这个过程中要用位运算来替代%运算符进行取模操作。
看这端代码:
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
i 值的结果就是值 threadLocalHashCode 对 INITIAL_CAPACITY 的取模运算。
这个threadLocalHashCode不就固定不变的,是一个累加的计数器,通过AtomicInteger来实现的。
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static intnextHashCode() {returnnextHashCode.getAndAdd(HASH_INCREMENT);
}
每次使用threadLocalHashCode值都把的nextHashCode累加一下
具体为什么2的N次幂 可以通过按位来计算取模的值,而其他的数子不可以,自己可以去测试一下。
4: 第3步是这个ThreadLocal.ThreadLocalMap不存在执行的过程,如果存在就设置一个值
1 private void set(ThreadLocal>key, Object value) {2
3 //We don't use a fast path as with get() because it is at4 //least as common to use set() to create new entries as5 //it is to replace existing ones, in which case, a fast6 //path would fail more often than not.
7
8 Entry[] tab =table;9 int len =tab.length;10 int i = key.threadLocalHashCode & (len-1);11
12 for (Entry e =tab[i];e != null;e = tab[i =nextIndex(i, len)]) {15 ThreadLocal> k =e.get();16
17 if (k ==key) {18 e.value =value;19 return;20 }21
22 if (k == null) {23 replaceStaleEntry(key, value, i);24 return;25 }26 }27
28 tab[i] = newEntry(key, value);29 int sz = ++size;30 if (!cleanSomeSlots(i, sz) && sz >=threshold)31 rehash();32 }
这个过程有点长,大概的步骤如下:
1、先通过 threadLocalHashCode取模获取到一个table中的位置
2、从当前位置开始查找,获取不为空的Entry
(1)判断一下位置上的 Entry 和当前的ThreadLocal(当然Entry也是一个ThreadLocal)是不是同一个,是的话数据就覆盖,返回
(2)不是同一个ThreadLocal,再判断一下位置上的ThreadLocal的值是不是空的(空的原因有两个,1是本来就没有,2是执行GC的时候被回收了,Entry是一个ThreadLocal弱引用) ,把新的value替换到当前位置上,返回
3、通过步骤2的两个都没有执行,则在步骤1中获取的位置上设置一个新的的Entry 。
4、计算tab中有效数据的长度,删除过期条目,如果存在的数据大小不符合要求,则将表的大小增加一倍。
T get()的实现过程 ,主要就三个方法:
1 publicT get() {2 Thread t =Thread.currentThread();3 ThreadLocalMap map =getMap(t);4 if (map != null) {5 ThreadLocalMap.Entry e = map.getEntry(this);6 if (e != null) {7 @SuppressWarnings("unchecked")8 T result =(T)e.value;9 returnresult;10 }11 }12 returnsetInitialValue();13 }
1 private Entry getEntry(ThreadLocal>key) {2 int i = key.threadLocalHashCode & (table.length - 1);3 Entry e =table[i];4 if (e != null && e.get() ==key)5 returne;6 else
7 returngetEntryAfterMiss(key, i, e);8 }
1 private Entry getEntryAfterMiss(ThreadLocal> key, inti, Entry e) {2 Entry[] tab =table;3 int len =tab.length;4
5 while (e != null) {6 ThreadLocal> k =e.get();7 if (k ==key)8 returne;9 if (k == null)10 expungeStaleEntry(i);11 else
12 i =nextIndex(i, len);13 e =tab[i];14 }15 return null;16 }
大概步骤如下:
1、获取当前线程中的 ThreadLocal.ThreadLocalMap
2、当前线程中存在ThreadLocal.ThreadLocalMap,
(1)将ThreadLocal的threadLocalHashCode取模的值为查找的当前位置。从当前值开始在table中查找,找到返回;
(2)一直找不到,就调用set方法给当前线程ThreadLocal.ThreadLocalMap设置一个初始值
下面是两个调试时两个ThreadLocal的编号截图
其中一个线程中ThreadLocal.ThreadLocalMap的对象threadLocals的截图数据:
因为我定义了两个ThreadLocal,所以threadLocals的table表中有两个entry,其他为空。
参考:http://www.cnblogs.com/xrq730/p/4854813.html
使用场景可参考:http://www.cnblogs.com/yxysuanfa/p/7125761.html