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

posted @ 2017-11-06 00:02  南极山  阅读(688)  评论(0)    收藏  举报