线程应用:(十)ThreadLocal

一、应用场景举例

  每个线程都应该有自己的数据库连接Connection,不能被其他线程所影响,就可以使用ThreadLocal(一个线程内各模块间共享同一数据,各线程间的数据又是独立的)。

二、使用举例

  线程范围内的共享变量,每个线程有自己独立的数据。例如每个线程要有自己独立的连接。ThreadLocal本质上是一个map。

  只要定义一个ThreadLocal变量,往这个变量里放的数据就是和线程相关的。一个ThreadLocal只能放一个变量,如果要存多个变量可以利用实体类。结合单例模式,把ThreadLocal定义在实体类。

public class ThreadTest4 {
    public static void main(String[] args){
        for(int i=0;i<2;i++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int data = new Random().nextInt();
                    System.out.println(Thread.currentThread().getName()+"has put data:" + data);
                    Entity.getInstance().setData(data);        //拿到本线程在这个类里对应的实例,再赋值
                    new A().get();
                    new B().get();
                }
            }).start();
        }
    }
}

class A {
    public void get(){
        Entity mydata = Entity.getInstance();
        System.out.println("A from "+Thread.currentThread().getName()+"has put data:" + mydata.getData());
    }
}

class B {
    public void get(){
        Entity mydata = Entity.getInstance();
        System.out.println("B from "+Thread.currentThread().getName()+"has put data:" + mydata.getData());
    }
}

//多变量实体类
class Entity{
    private Entity(){}
    private static ThreadLocal<Entity> map = new ThreadLocal<Entity>();    //在这个类中定义一个ThreadLocal,存每个线程对应的实例
    public static /*synchronized*/ Entity getInstance(){    //一般单例加synchronized会更严谨一点,这里取的就是本线程的实体类,所以可不加
        Entity instance = map.get();
        if(instance == null){        //如果这个线程还没有对应的实例,才往ThreadLocal存
            instance = new Entity();
            map.set(instance);        
        }
        return instance;
    }
    int data;
    public int getData() {
        return data;
    }
    public void setData(int data) {
        this.data = data;
    }
}

运行结果:
Thread-0has put data:2114567887
Thread-1has put data:-746942766
A from Thread-1has put data:-746942766
A from Thread-0has put data:2114567887
B from Thread-0has put data:2114567887
B from Thread-1has put data:-746942766

三、源码分析

1、get()方法

//ThreadLocal的get()方法
public T get() {
    Thread t = Thread.currentThread();        //1、得到当前线程
    ThreadLocalMap map = getMap(t);            //2、从当前线程获得threadLocals成员变量
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);    //3、从threadLocals得到对应线程数据
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

//2
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

//3
private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

  看完了get()方法,我发现ThreadLocal和我想象的不一样,我一开始以为ThreadLocal本身就是一个map数据结构,键是线程id,但其实真正存储数据的地方是在每个Thread对象的 threadLocals属性中,threadLocals 属性是一个 ThreadLocal.ThreadLocalMap 对象。并且通过ThreadLocal的threadLocalHashCode指定数据存储在数组的哪个位置下。

  可能接下来就有疑问:ThreadLocalMap的数据结构是怎么样的?又是怎么存储数据的呢?

2、ThreadLocalMap数据结构

//Thread类的成员变量threadLocals
public class Thread implements Runnable {
...    
    ThreadLocal.ThreadLocalMap threadLocals = null;
...

  

//ThreadLocal.ThreadLocalMap
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;
        }
    }

    private static final int INITIAL_CAPACITY = 16;

    private Entry[] table;

...

  ThreadLocal.ThreadLocalMap 是 ThreadLocal 的一个静态内部类。可以看出,ThreadLocalMap实际是使用一个数组 private Entry[] table 来存储数据,初始大小为16,类型为Entry。

   对于Entry类型,一开始我看 Entry 只有一个value属性,就有一个疑问,为什么 Entry[] table 这个数组是一个map数组,而不是value数组。更仔细地查看源码后,发现Entry真的应该是一个键值对数组,并以 ThreadLocal<?>作为key,且这个key被弱引用类型包装 WeakReference<ThreadLocal<?>>。从ThreadLocalMap 的 getEntry() 方法可以看出进行了一次key的比对,而这里的key就是Entry的referent属性,且referent被一个弱引用包装。

private Entry getEntry(ThreadLocal<?> key) {
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

public T get() {
    return this.referent;
}

  综上,我们知道,ThreadLocal 并不是真正存放数据的数据结构,而是被弱引用包装,做为每个线程Thread对象中ThreadLocalMap类型属性的key。

  为什么不是把ThreadLocal当成map,以线程ID为key这样的方式,而是以这样的方式来存放数据,其实思考一下,是能发现其可取之处的,对于后一种方式,当线程结束后,相关的数据会跟随一起被回收。如果把ThreadLocal当成map,理论上也能实现,但就可能会出现线程结束但它的相关资源仍存在的情况。 

3、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);
}

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);    //根据threadLocalHashCode找数组中对应的位置

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {                //看key是否存在,存在替换掉
            e.value = value;
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);        //不存在,新建
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

四、ThreadLocal内存泄漏的问题

  根据上面Entry方法的源码,我们知道ThreadLocalMap是使用ThreadLocal的弱引用作为Key的。JVM中弱引用在垃圾回收时,不管内存有没有占满,都会被GC回收。因此很有可能在某次GC之后,某个线程的某个ThreadLocal变量变成了null,那么在Entry中,Key也变成了null,在查找时将永远不会被找到,这个Entry的Value将永远不会被用到,这就是内存泄漏。 

为了防止此类情况的出现,我们有两种手段。

  1、手动调用ThreadLocal的remove函数,手动删除不再需要的ThreadLocal,防止内存泄露

  2、JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。

  

参考链接:

 http://www.cnblogs.com/digdeep/p/4510875.html

 https://www.cnblogs.com/qiuyong/p/7091689.html

 http://www.importnew.com/22039.html

 https://blog.csdn.net/lhqj1992/article/details/52451136

 

https://blog.csdn.net/fly910905/article/details/78869251

https://www.cnblogs.com/xzwblog/p/7227509.html

https://www.cnblogs.com/coshaho/p/5127135.html

https://www.cnblogs.com/dolphin0520/p/3920407.html

http://blog.51cto.com/2179425/2082743

https://blog.csdn.net/woshiluoye9/article/details/72544764

https://blog.csdn.net/L_BestCoder/article/details/79252620

posted @ 2018-08-19 17:42  湮天霸神666  阅读(158)  评论(0编辑  收藏  举报