java多线程-Threadlocal讲解
ThreadLocal感觉并不是很常用,但是在使用kryo进行序列化时出现了这个东西,说kryo序列化是非线程安全的,可以使用ThreadLocal来达到线程安全。
ThreadLocal在JDK 1.2中就已经存在了,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。
使用这个工具类可以很方便的使用多线程来进行参数传递,并且线程间的数据是隔离,不同线程之间的数据不会相互干扰.。
定义一个全局的ThreadLocal,这个ThreadLocal能够放多个线程级别的变量,可是它又能够被多个线程共享使用,并且又能够达到线程安全的目的,且绝对线程安全。
我们先来看一个简单的ThreadLocal使用例子:
1 public class ThreadLocalTest { 2 3 private static ThreadLocal<String> threadLocalA = new ThreadLocal<String>(); 4 private static ThreadLocal<String> threadLocalB = new ThreadLocal<String>(); 5 6 public static void main(String[] args) { 7 8 for (int i = 0; i < 2; i++) { 9 final int iData = i; 10 Thread thread = new Thread(new Runnable() { 11 public void run() { 12 threadLocalA.set("A" + iData); 13 threadLocalB.set("B" + iData); 14 new TestA().get(); 15 new TestB().get(); 16 } 17 }); 18 19 thread.start(); 20 } 21 } 22 23 static class TestA { 24 public void get() { 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 class TestB { 31 public void get() { 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<ThreadLocal<?>> { /** 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 int nextHashCode() { return nextHashCode.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 at 4 // least as common to use set() to create new entries as 5 // it is to replace existing ones, in which case, a fast 6 // 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] = new Entry(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 public T 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 return result; 10 } 11 } 12 return setInitialValue(); 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 return e; 6 else 7 return getEntryAfterMiss(key, i, e); 8 }
1 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, 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 return e; 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

浙公网安备 33010602011771号