ThreadLocal(二)

2018-01-24  本文已影响0人  50e383299c68
  1. 实现原理
    ThreadLocal可以看做是一个容器,容器里面存放着属于当前线程的变量。
    ThreadLocal类提供了四个接口方法,这也是用户操作ThreadLocal类的基本方法:
    (1) void set(Object value)设置当前线程的线程局部变量的值。
    (2) public Object get()该方法返回当前线程所对应的线程局部变量。
    (3) public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。防止内存泄漏。例如在线程池中,可能该线程一直存在,线程中的ThreadLocal对象也会一直存在。
    (4) protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次,ThreadLocal中的缺省实现直接返回一个null。

ThreadLocal内部是如何为每一个线程维护变量副本的呢?其实在Thread类中有一个成员变量ThreadLocal.ThreadLocalMap threadLocals 存储线程实例的变量副本。ThreadLocalMap是ThreadLocal类的静态内部类。这个变量threadLocals的维护都是由ThreadLocal操作。
但是ThreadLocalMap不是map类型,而是数组类型,通过ThreadLocal对象的hashcode计算对应的数组索引,数组存储的value是线程中变量的副本,每个线程可能存在多个ThreadLocal对象。

public T get() {
    Thread t = Thread.currentThread();//当前线程
    ThreadLocalMap map = getMap(t);//获取当前线程对应的ThreadLocalMap
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);//获取对应ThreadLocal的变量值
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();//若当前线程还未创建ThreadLocalMap,则返回调用此方法并在其中调用createMap方法进行创建并返回初始值。
} 
//设置变量的值
public void set(T value) {
   Thread t = Thread.currentThread();
   ThreadLocalMap map = getMap(t);
   if (map != null)
       map.set(this, value);
   else
       createMap(t, value);
}

/**
为当前线程创建一个ThreadLocalMap的threadlocals,并将第一个值存入到当前map中
@param t the current thread
@param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}  
//删除当前线程中ThreadLocalMap对应的ThreadLocal
public void remove() {
       ThreadLocalMap m = getMap(Thread.currentThread());
       if (m != null)
           m.remove(this);
}

上述是在ThreadLocal类中的几个主要的方法,他们的核心都是对其内部类ThreadLocalMap进行操作,下面看一下该类的源代码:

static class ThreadLocalMap {
  //map中的每个节点Entry,其键key是ThreadLocal并且还是弱引用,这也导致了后续会产生内存泄漏问题的原因。
 static class Entry extends WeakReference<ThreadLocal<?>> {
           Object value;
           Entry(ThreadLocal<?> k, Object v) {
               super(k);
               value = v;
   }
    /**
     * 初始化容量为16,以为对其扩充也必须是2的指数 
     */
    private static final int INITIAL_CAPACITY = 16;
    /**
     * 真正用于存储线程的每个ThreadLocal的数组,将ThreadLocal和其对应的值包装为一个Entry。
     */
    private Entry[] table;
}
  1. 内存泄漏问题
    线程执行完操作后如果只是将ThreadLocal对象置为null。例如 ThreadLocal<Integer> seqNum = null。会造成内存泄漏。原因如下:ThreadLocal是个容器,容器里面的对象没有处理。容器里面的对象的引用存储在ThreadLocalMap中。如果线程操作完后,还一直存在在线程池里面,这个线程的ThreadLocalMap也一直存在,ThreadLocalMap里面的变量副本的引用也一直存在。这样导致变量副本也一直存在,造成内存泄漏。
    解决方法:ThreadLocal对象使用完之后调用ThreadLocal.remove()。ThreadLocal对象的remove()方法会把ThreadLocalMap中对应的变量副本的引用删掉。
上一篇下一篇

猜你喜欢

热点阅读