ThreadLocal
ThreadLocal
类用于保存线程局部变量。这些变量与普通变量的不同之处在于,每个访问变量的线程(通过get()
或set()
方法)都有自己独立初始化的变量副本。ThreadLocal
类的实例通常是类中的私有静态字段。所存放的变量一般是会和当前线程所绑定,(例如,登陆用户的用户ID或事务状态)。
类设计
看类图,ThreadLocal只有一个静态内部类ThreadLocalMap,SuppliedThreadLocal是在1.8增加的。属实本地线程隔离全靠ThreadLocalMap来实现。
ThreadLocalMap
该类是包私有的,ThreadLocalMap
是一个自定义哈希表,用于维护线程本地变量。在ThreadLocal类之外无法进行任何访问。同时Thread中类中有ThreadLocal.ThreadLocalMap
字段的声明,用于父子线程继承局部变量,哈希表entry使用弱引用,是为了处理非常大且长期存在。对于不调用remove方法时候。只有当表开始用完空间时,旧的数据的条目才会被删除设置为null。在gc时候被回收。(即entry.get()== null)意味着键不再被引用,才可以从表中删除。
SuppliedThreadLocal
继承了ThreadLocal,内部组合函数接口Supplier
,用于设定初始值,使用构造传入。
源码分析
public class ThreadLocalDemo {
private static final ThreadLocal<Map<String,String>> threadLocal =ThreadLocal.withInitial(()->new HashMap<String,String>(){{put("init","我最美");}});
public static void main(String[] args){
CountDownLatch countDownLatch=new CountDownLatch(1);
new Thread(()->{
System.out.println(threadLocal.get());
threadLocal.set(new HashMap<String,String>(){{put("subThread","bb");}});
System.out.println(threadLocal.get());
countDownLatch.countDown();
}).start();
try {
countDownLatch.await();
System.out.println(threadLocal.get());
threadLocal.set(new HashMap<String,String>(){{put("main","bb");}});
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println(threadLocal.get());
}
}
output:
{init=我最美}
{subThread=bb}
{init=我最美}
{main=bb}
Process finished with exit code 0
一个简简单单的代码中可以看到ThreadLocal和SuppliedThreadLocal
均无法在父子线程中实现变量的继承,但是实现了变量的线程隔离。废话,人家本来就是干这事需要你说。啧啧啧
构造方法未使用默认无参构造使用ThreadLocal.withInitial
是为了演示SuppliedThreadLocal
的使用方式,可以实现某些场景如getOrNotExistDefault
。
设置变量
创建ThreadLocal
对象之后,并未对ThreadlocalMap进行初始化。在对其进行写入时会,进行调用createMap来初始化ThreadlocalMap初始化是则是初始化Thread内部ThreadLocal.ThreadLocalMap
字段,是不是明白怎么实现线程隔离了。那个线程的内部变量,可不就是私有,西八。调用构造方法创建entry数组。注意此entry对象是弱引用。当在GC线程扫描它所管辖的内存区域的过程中,一旦发现了只有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。这样在Entry中key会被回收 但是value不会被回收,这就是造成内存泄漏。
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
读取变量
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();
}
在get变量的同时如果发现当前的key获取不到,判断this.referent
是否为null,如果为空会设置当前value为null,设置当前的entry的数组下标对象为空,更新容量,方便在下一次GC的时候回收。如果有默认值在取不到值得情况下返回默认值 反之返回null;
回收变量,删除变量
public void remove() {
ThreadLocalMap m=getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
删除变量的同时会从更具当前的线程获取到当前线程的ThreadLocal.ThreadLocalMap
字段从其中读取key,当命中之后。会设置this.referent
是否为null,设置key为null设置value为null设置中,entry对应的数组下标值为null方便GC。
最佳实践
使用ThreadLocal要避免在线程池当中使用,这样变量极容易泄漏。还有使用之时设置变量,在使用完毕之后需要手动调用remove方法来删除。
延伸阅读InheritableThreadLocal
InheritableThreadLocal
类提供值的继承从父线程到子线程:在创建子线程时,子接收所有可继承线程局部变量的初始值父线程的值。 一般来说,子线程的值将是与父线程相同;但子线程的值可以通过重写childCValue方法来更改值。
public class ThreadLocalDemo {
private static final InheritableThreadLocal<Map<String,String>> threadLocal =new InheritableThreadLocal<>();
public static void main(String[] args){
threadLocal.set(new HashMap<String,String>(){{put("mailThread","bb");}});
CountDownLatch countDownLatch=new CountDownLatch(2);
new Thread(()->{
countDownLatch.countDown();
System.out.println(threadLocal.get());
Map<String,String> t=threadLocal.get();
t.put("SubThread1","aaa");
threadLocal.set(t);
}).start();
new Thread(()->{
countDownLatch.countDown();
System.out.println(threadLocal.get());
Map<String,String> t=threadLocal.get();
t.put("SubThread2","aaa");
threadLocal.set(t);
}).start();
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(threadLocal.get());
}
}
output
{mailThread=bb}
{mailThread=bb, SubThread1=aaa}
{mailThread=bb, SubThread1=aaa, SubThread2=aaa}
Process finished with exit code 0
总结
ThreadLocal提供了本地线程私有变量,主要用于线程隔离,互不影响,相当于每个线程均保留一份副本,另一层面的解决了竞态访问。但是由于WeakReference
的key导致了在没有强引用应用情况下,会被GC回收,但是对应的value值却无法回首,然而ThreadLocal类只有对其进行访问或者容量满了之后才回去删除过期的Entry,这就容易造成内存泄漏。用完之后remove的习惯要写在回忆里。还有当需要在父子线程传输变量可以使用InheritableThreadLocal
.