深入理解ThreadLocal

网友投稿 566 2022-11-26

深入理解ThreadLocal

深入理解ThreadLocal

ThreadLocal是一个和线程安全相关的类。

一个非线程安全的例子

在我们讲述它之前,我们先看一个例子。

package thread;public class NotSafeThread implements Runnable{ private int a=10; public void run() { // TODO Auto-generated method stub for (int i = 0; i < 5; i++) { try { if (Thread.currentThread().toString() .equals("Thread[t2,5,main]") && i == 2) Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().toString() + " a=" + a++); } }}

package thread;public class TestThread { public static void main(String[] args) { NotSafeThread nst=new NotSafeThread(); Thread t1=new Thread(nst,"t1"); Thread t2=new Thread(nst,"t2"); Thread t3=new Thread(nst,"t3"); t1.start(); t2.start(); t3.start(); }}

输出的结果:a在各个线程中会累积。

Thread[t1,5,main]   a=10

Thread[t1,5,main]   a=11

Thread[t1,5,main]   a=12

Thread[t2,5,main]   a=13

Thread[t2,5,main]   a=14

Thread[t3,5,main]   a=15

Thread[t3,5,main]   a=16

Thread[t3,5,main]   a=17

Thread[t3,5,main]   a=18

Thread[t3,5,main]   a=19

Thread[t1,5,main]   a=20

Thread[t1,5,main]   a=21

Thread[t2,5,main]   a=22

Thread[t2,5,main]   a=23

Thread[t2,5,main]   a=24

解决这个问题,有各种办法例如把a移动到run方法里面,给run加上synchronized等等。

不过为了解决这个问题我们还有一种方式:使用ThreadLocal来维护a。

ThreadLocal的简单介绍

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。

当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

我们先看应用再讲原理,然后再讲一个实际的应用。

第一个应用

public class SafaThread { private static ThreadLocal counterContext=new ThreadLocal(); public static Integer getCounterContext(){ if (counterContext.get()!=null) return counterContext.get(); setCounterContext(10); return counterContext.get(); } public static void setCounterContext(Integer value){ counterContext.set(value); } public static Integer getCounterContextNext(){ counterContext.set(getCounterContext()+1); return counterContext.get(); }}

package thread;public class ThreadLocalTest extends Thread{ public void run() { for (int i = 0; i < 5; i++) System.out.println(Thread.currentThread().getName()+" a="+ SafaThread.getCounterContextNext()); } public static void main(String[] args) { ThreadLocalTest t=new ThreadLocalTest(); Thread t1=new Thread(t); Thread t3=new Thread(t); Thread t2=new Thread(t); t1.start(); t2.start(); t3.start(); }}

结果如下:

Thread-1   a=11

Thread-1   a=12

Thread-1   a=13

Thread-1   a=14

Thread-1   a=15

Thread-3   a=11

Thread-3   a=12

Thread-3   a=13

Thread-3   a=14

Thread-3   a=15

Thread-2   a=11

Thread-2   a=12

Thread-2   a=13

Thread-2   a=14

Thread-2   a=15

完全符合我们的要求。

现在重头戏来了,看看ThreadLocal实现的原理。

ThreadLocal类方法很简单,只有4个方法,我们先来了解一下:

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

public Object get()该方法返回当前线程所对应的线程局部变量。

public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。

除了上面的方法介绍外,ThreadLocal里还有一个静态类:

static class ThreadLocalMap { static class Entry extends WeakReference { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } /** * The table, resized as necessary. * table.length MUST always be a power of two. */ private Entry[] table; ......//省略其他代码}

ThreadLocalMap里面还有一个静态类,也是醉了..静态类是Entry。

Entry里面存放的是当前的ThreadLocal和这个线程中的成员变量。

当前线程的成员变量?怎么来的?

看这个:

public class Thread implements Runnable { /* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null; ....//省略代码 }

看到了吧,线程这个基类本身里面就有一个ThreadLocalMap。

好啦 现在我们开启debug模式。

System.out.println(Thread.currentThread().getName()+" a="+ SafaThread.getCounterContextNext());

ok,进去

public static Integer getCounterContextNext(){ counterContext.set(getCounterContext()+1); return counterContext.get(); }

最先执行的是getCounterContext()。

public static Integer getCounterContext(){ if (counterContext.get()!=null) return counterContext.get(); setCounterContext(10); return counterContext.get(); }

我们看看counterContext(它本身是ThreadLocal)的get方法。如下:

public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); //标注1 if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); //标注3 if (e != null) return (T)e.value; } return setInitialValue(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }

当我们第一次调用SafaThread.getCounterContextNext()的时候,直到上面的标注1处,返回的map肯定为空。我们看看setInitialValue

private T setInitialValue() { T value = initialValue(); //标注2 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }

至于creatMap里面的代码

void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }

我们再看看ThreadLocalMap的构造函数。你妹呀,你想累死老子,都最后一步了,自己看!

看看上面的标注2

protected T initialValue() { return null; }

现在我们看看,看看什么?看ThreadLocal的set方法。中间的我不讲了。太累了。

public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }

跟get一样是吧。

这下大家都明白了吧。

我说几个关键点

ThreadLocalMap里面有个Entry数组,数组里面放的元素由两部分组成

static class Entry extends WeakReference { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } }

key是ThreadLocal,value是要存储的值。

key为什么不是当前线程?而是ThreadLocal呢?

自己想。想不明白再问我。

在上面的情况下,一个线程里的ThreadLocalMap的Entry的size只能是1。

为什么?看ThreadLocal的set方法。

一个问题

public class SafaThread { private static ThreadLocal counterContext=new ThreadLocal(); private static ThreadLocal counterContext2=new ThreadLocal(); ...//省略其他代码 本来有三个方法 现在就是六个}

本来只有一个变量,我现在放两个。

会怎么样。

答案就是:

key为什么不是线程?而是ThreadLocal呢?

Entry各个元素的区别到底在哪?

标注3的this是什么?

亲 你懂了没?

好吧,我偷的一手好懒。

下一个应用

我们看看struts中是如何运用ThreadLocal的。

感谢glt

总结

ThreadLocal最适合使用的场景:

在同一个线程的不同开发层次共享数据

使用ThreadLocal的步骤

1建立一个类,在其中封装静态的ThreadLocal变量,使其成为一个数据共享环境

2在类中实现ThreadLoca变量的静态方法(设值与取值)

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Gradle 创建Task的多种方法
下一篇:vs2010+ Ankhsvn使用详解
相关文章

 发表评论

暂时没有评论,来抢沙发吧~