java多线程threadlocal_java多线程-Threadlocal讲解

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对象

2a8f91301c3ff41a9e5d671a9cabd7ff.png

ThreadLocalMap是ThreadLocal 的一个内部类,数据的存储就是通过他来实现的。

要搞清工作原理,就要分析一下源码, 查看ThreadLocal源码,它是一个泛型的类,类中主要的三个public方法

set(T value) 设置当前线程的线程局部变量的值。

T get()返回当前线程所对应的线程局部变量。

void remove()将当前线程局部变量的值删除

set(T value)方法的实现

cfeff2f2fc503a175b87a6413010ae87.png

执行大概分以下三步:

1、获取取得当前的线程。

2、获取线程里面ThreadLocal.ThreadLocalMap的对象

891e4f6c087817e18642901abc1c75e7.png

3、看这个ThreadLocal.ThreadLocalMap是否存在,不存在创建一个ThreadLocal.ThreadLocalMap,并且把它赋值给线程的 threadLocals 变量。

创建的过程代码如下:

4f3662a43fed55da79b8bfc7e7d9d8c8.png

13168a493d18252eeba4a8849d8a5b83.png

创建的过程步骤如下:

生成一个 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的编号截图

400e36b5386ca813f3587f2ea2d935db.png

其中一个线程中ThreadLocal.ThreadLocalMap的对象threadLocals的截图数据:

80f1450e8c336eb017b5cf1184ae05ae.png

因为我定义了两个ThreadLocal,所以threadLocals的table表中有两个entry,其他为空。

c8f602953964657c4855b39f0177cd73.png

13698d4b03c933d702f8dae1470851fe.png

参考:http://www.cnblogs.com/xrq730/p/4854813.html

使用场景可参考:http://www.cnblogs.com/yxysuanfa/p/7125761.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值