深入理解ThreadLocal

来源:互联网 发布:美工小刀片 编辑:程序博客网 时间:2024/06/05 12:49

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

本文修订版见

http://blog.csdn.net/dlf123321/article/details/43153717


一个非线程安全的例子

在我们讲述它之前,我们先看一个例子。
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<Integer> counterContext=new ThreadLocal<Integer>();        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<T>。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。
    
    除了上面的方法介绍外,ThreadLocal里还有一个静态类:
    static class ThreadLocalMap {        static class Entry extends WeakReference<ThreadLocal> {            /** 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<Integer>)的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<ThreadLocal> {            /** 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<Integer> counterContext=new ThreadLocal<Integer>();    private static ThreadLocal<Integer> counterContext2=new ThreadLocal<Integer>();    ...//省略其他代码 本来有三个方法 现在就是六个}
本来只有一个变量,我现在放两个。
会怎么样。
答案就是:
key为什么不是线程?而是ThreadLocal呢?
Entry各个元素的区别到底在哪?
标注3的this是什么?
亲 你懂了没?
好吧,我偷的一手好懒。


下一个应用

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

感谢glt


总结

ThreadLocal最适合使用的场景:

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

使用ThreadLocal的步骤

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

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


参考资料

先看a再看b

a:  http://lavasoft.blog.51cto.com/62575/51926/

b:  http://blog.csdn.net/lufeng20/article/details/24314381struts2技术内幕

c:  struts2技术内幕  4.1节

0 0