ThreadLocal详解

多个线程需要对一个共享变量写入时容易出现并发问题。
ThreadLocal可以做到:当多个线程对一个共享变量进行访问的时候,实际上访问的是本线程的本地变量。

一、ThreadLocal实现原理

Thread类中有threadlocals变量,threadlocals变量的类型是ThreadLocal.ThreadLocalMap,是在ThreadLocal类中实现的一个类似于Map的结构,ThreadLocalMap结构实际上是一个Entry对,key值是ThreadLocal对象,value值是ThreadLocal对象的值,如下图所示:

二、ThreadLocal实现细节

1、get实现

    public T get() {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的threadLocals变量
        ThreadLocalMap map = getMap(t);
        //如果当前线程的threadLocals变量不为空,则返回对应的value值
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果当前线程threadLocals变量为null,则初始化当前线程的threadLocals变量
        return setInitialValue();
    }
    private T setInitialValue() {
        //定义value = null
        T value = initialValue();
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的threadLocals变量
        ThreadLocalMap map = getMap(t);
        //如果当前线程的threadLocals变量不为空,则将当前的threadLocals变量的value值置为null
        if (map != null)
            map.set(this, value);
       //如果当前线程的threadLocals变量为空,则初始化创建一个当前线程的threadLocals变量,并将value置为null
        else
            createMap(t, value);
        return value;
    }

2、set实现

    public void set(T value) {
        //获取当前线程
        Thread t = Thread.currentThread();
        //获取当前线程的threadLocals变量
        ThreadLocalMap map = getMap(t);
        //如果当前线程的threadLocals变量不为空,则将该变量的value值设置为传入的value值
        if (map != null)
            map.set(this, value);
        //如果当前线程的threadLocals变量为空,则初始化创建一个当前线程的threadLocals变量,并将该变量的value值设置为传入的value值
        else
            createMap(t, value);
    }

3、remove实现

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

三、内存泄露

我们先看看ThreadLocal态类里面的成员:

public class ThreadLocal<T>{
      //......

      static class ThreadLocalMap{
            static class Entry extends WeakReference<ThreadLocal<?>>{
                  Object value;
                  
                  Entry(ThreadLocal<?> k, Object v){
                        super(k);
                        value = v;
                  }
            }
            //......
      }
      //......
}

当一个线程调用ThreadLocal类的set方法设置当前线程的本地变量时,当前线程的ThreadLocalMap中会有一条记录,该记录的key值为ThreadLocal的弱引用,value为set方法设置的值。

如果当前线程一直存在,且没有调用ThreadLocal的remove方法,且这时候还有其他地方还有对ThreadLocal的引用,则当前线程的ThreadLocalMap变量里会同时存在对ThreadLocal变量的引用和对value对象的引用,它们不会被释放,因此会造成内存泄露。

由于线程的ThreadLocalMap中的key是弱引用,所以当前线程的ThreadLocalMap中的ThreadLocal变量的弱引用会在下一次gc时被回收,但是对应的value还是会造成内存泄露,这时候ThreadLocalMap中会存在key为null但是value不为null的Entry项。

解决方法:在每次使用完毕后调用remove方法。

四、线程安全
那么 ThreadLocal到底是不是线程安全的呢?NONONO!想要拥有线程安全,Value必须是不可变对象。

posted @ 2020-12-30 15:29  西瓜味的可乐酱  阅读(112)  评论(0)    收藏  举报