多线程下ThreadLocal的使用

ThreadLocal入门

ThreadLocal是什么

首先,它是一个数据结构,有点像HashMap,可以保存key-value键值对,但是一个ThreadLocal只能保存一个,并且各个线程的数据互不干扰。

ThreadLocal localValue = new ThreadLocal();
localValue.set("Here is a value");
String value = localVlaue.get();

在线程1中初始化了一个ThreadLocal对象localName,并通过set方法,保存了一个String类型的变量,同时在线程1中通过 localName.get()可以拿到之前设置的值,但是如果在线程2中,拿到的将是一个null。

ThreadLocal做什么

ThreadLocal提供一个线程(Thread)局部变量,访问到某个变量的每一个线程都拥有自己的局部变量。说白了,ThreadLocal就是想在多线程环境下去保证成员变量的安全。

ThreadLocal如何做的

其set 及 get 源码如下:

public void set(T value) {
   Thread t = Thread.currentThread();
   ThreadLocalMap map = getMap(t);
   if (map != null)
       map.set(this, value);
   else
       createMap(t, value);
}
public T get() {
   Thread t = Thread.currentThread();
   ThreadLocalMap map = getMap(t);
   if (map != null) {
       ThreadLocalMap.Entry e = map.getEntry(this);
       if (e != null) {
           @SuppressWarnings("unchecked")
           T result = (T)e.value;
           return result;
       }
   }
   return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
   return t.threadLocals;
}

ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中有一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。
这里可以查看更详细的关于ThreadLocal数据结构的分析

ThreadLocal的使用

主要API

ThreadLocal类接口主要有四个方法:

接口 作用
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。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。

需要注意的问题

内存泄漏

这里主要与ThreadLocal的底层数据结构为Entry有关,

static class Entry extends WeakReference> {
   /** The value associated with this ThreadLocal. */
   Object value;
   Entry(ThreadLocal k, Object v) {
       super(k);
       value = v;
   }
}

当使用ThreadLocal保存一个value时,会在ThreadLocalMap中的数组插入一个Entry对象,按理说key-value都应该以强引用保存在Entry对象中,但在ThreadLocalMap的实现中,key被保存到了WeakReference对象中。

这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。

既然已经发现有内存泄露的隐患,自然有应对的策略,在调用ThreadLocal的get()、set()可能会清除ThreadLocalMap中key为null的Entry对象,这样对应的value就没有GC Roots可达了,下次GC的时候就可以被回收,当然如果调用remove方法,肯定会删除对应的Entry对象。

如果使用ThreadLocal的set方法之后,没有显示的调用remove方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完ThreadLocal之后,记得调用remove方法。

你可能感兴趣的:(Java,多线程,数据结构)