码迷,mamicode.com
首页 > 其他好文 > 详细

ThreadLocal

时间:2020-06-17 13:11:58      阅读:58      评论:0      收藏:0      [点我收藏+]

标签:回收   数据库连接   场景   loading   不同   代码   初始化   weakref   private   

1. 什么是ThreadLocal?

多线程在访问同一个共享变量操作时,尤其是写入操作时,为了保证线程安全,都会做一些额外的同步措施。ThreadLocal提供了另一种不靠加锁就能保证线程安全的方式。ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的都是自己本地内存中的变量,从而规避线程安全问题。

技术图片

 

 

 2.ThreadLocal的实现原理

ThreadLocal有一个内部静态类ThreadLocalMap,然后在Thread类中,有两个成员变量:threadLocals 和 inheritableThreadLocals,这两个都是ThreadLocalMap类型的。

技术图片

 

 

 技术图片

 

 

 

这两个变量的默认值都是null,只有在线程第一次调用threadlocal的set或者get方法时,才会创建它们。每个线程的变量副本并不是存放在threadlocal实例中,而是存放在thread类中的threadlocals中,当取值的时候,用get方法从threadlocals中获取变量;

  1. set方法解析
public void set(T value) {
    //(1)获取当前线程(调用者线程)
    Thread t = Thread.currentThread();
    //(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
    ThreadLocalMap map = getMap(t);
    //(3)如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
    if (map != null)
  // this 是指的在程序中创建的threadlocal变量,比如:在程序中创建了多个threadlocal变量
// ThreadLocal<String> a = new ThreadLocal<>(); ThreadLocal<Integer> b = new ThreadLocal<>();
  // 这个this就是指代a、b;
  map.set(this, value);  
//(4)如果map为null,说明首次添加,需要首先创建出对应的map
    else
        createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
}

    2. get方法解析

public T get() {
    //(1)获取当前线程
    Thread t = Thread.currentThread();
    //(2)获取当前线程的threadLocals变量
    ThreadLocalMap map = getMap(t);
    //(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    //(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
    return setInitialValue();
}

private T setInitialValue() {
    //protected T initialValue() {return null;}
    T value = initialValue();
    //获取当前线程
    Thread t = Thread.currentThread();
    //以当前线程作为key值,去查找对应的线程变量,找到对应的map
    ThreadLocalMap map = getMap(t);
    //如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
    if (map != null)
        map.set(this, value);
    //如果map为null,说明首次添加,需要首先创建出对应的map
    else
        createMap(t, value);
    return value;
}

    3. remove方法解析

public void remove() {
    //获取当前线程绑定的threadLocals
     ThreadLocalMap m = getMap(Thread.currentThread());
     //如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量
     if (m != null)
         m.remove(this);
 }

3.内存泄露问题

ThreadLocal的数据其实都是存放在ThreadLocalMap中,而ThreadLocalMap内部实际上是一个Entry数组,这个Entry的内部类如下:

/**
 * 是继承自WeakReference的一个类,该类中实际存放的key是
 * 指向ThreadLocal的弱引用和与之对应的value值(该value值
 * 就是通过ThreadLocal的set方法传递过来的值)
 * 由于是弱引用,当get方法返回null的时候意味着坑能引用
 */
static class Entry extends WeakReference<ThreadLocal<?>> {
    /** value就是和ThreadLocal绑定的 */
    Object value;

    //k:ThreadLocal的引用,被传递给WeakReference的构造方法
    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
//WeakReference构造方法(public class WeakReference<T> extends Reference<T> )
public WeakReference(T referent) {
    super(referent); //referent:ThreadLocal的引用
}

//Reference构造方法
Reference(T referent) {
    this(referent, null);//referent:ThreadLocal的引用
}

Reference(T referent, ReferenceQueue<? super T> queue) {
    this.referent = referent;
    this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}

从代码中可以看出,threadLocal的引用被传递到weakReference的构造函数中【super(key)】,所以threadlocalMap中的key是一个threadlocal的弱引用,而弱引用在gc时会被直接回收掉,就有可能造成ThreadLocaMap中的key为null,而value不是null。这样的话,如果线程一直存在,那么threadLocaMap不会被回收,所以里面存储的threadlocal的entry就会越来越多,直到内存溢出。所以,每次使用完毕之后,需要显示的调用remove方法,删除threadlocalmap。

4.使用场景

session管理,区分不同线程的session信息;

数据库连接;

 

ThreadLocal

标签:回收   数据库连接   场景   loading   不同   代码   初始化   weakref   private   

原文地址:https://www.cnblogs.com/anduodefeng/p/13151616.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!