三. ThreadLoacl 基础
-
在Java的多线程编程中,为保证多个线程对共享变量的安全访问,通常会使用synchronized来保证同一时刻只有一个线程对共享变量进行操作。 但在有些情况下,synchronized不能保证多线程对共享变量的正确读写。例如类有一个类变量,该类变量会被多个类方法读写,当多线程操作该类的实例对象时,如果线程对类变量有读取、写入操作就会发生类变量读写错误,即便是在类方法前加上synchronized也无效,因为同一个线程在两次调用方法之间时锁是被释放的,这时其它线程可以访问对象的类方法,读取或修改类变量。 这种情况下可以将类变量放到ThreadLocal类型的对象中,使变量在每个线程中都有独立拷贝,不会出现一个线程读取变量时而被另一个线程修改的现象,ThreadLocal的作用则恰恰是为了实现线程的不共享,即实现线程所所保留的副本各不冲突的一个作用,(可以理解为是为线程提供一个线程私有的变量副本,这样多个线程都可以随意更改自己线程局部的变量,不会影响到其他线程)从而实现并发的另类的一种解决和实现思路,但是ThreadLocal如果不谨慎使用,导致强引用等的关系出现,使其线程始终没被回收,则也会出现内存溢出等的现象
-
ThreadlLocal 底层通过内部类ThreadLocalMap,实现,会将当前线程Thread作为Key,数据作为值进行存储,ThreadLocalMap中还有一个内部类Entry
-
ThreadLocal 注意点:
- JVM利用设置ThreadLocalMap的Key为弱引用,来避免内存泄露。
- JVM利用调用remove、get、set方法的时候,回收弱引用。
- ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链,造成内存泄漏
- 当使用static ThreadLocal的时候,延长ThreadLocal的生命周期,那也可能导致内存泄漏。因为,static变量在类未加载的时候,它就已经加载,当线程结束的时候,static变量不一定会回收。那么,比起普通成员变量使用的时候才加载,static的生命周期加长将更容易导致内存泄漏危机。
- 么如何有效的避免ThreadLocalMap中的key也就是ThreadLocal被垃圾回收为null时造成的内存泄露
a. 在使用完毕后手动调用ThreadLocal的remove方法手动进行释放
- 使用示例
- 存有需要设置线程私有变量的类Res ,该类中有一个计数变量,每次执行累计加1线程私有threadLocal
class Res {
private Integer count;
/*使用Threadlocal创建一个属于每个线程的变量
* 只要执行这段代码的线程都会拥有这个变量,变量名就可以看做是Threadlocal对象名
* 并且每个线程修改这个变量时,修改的是各自的副本,不影响其他线程的使用
* 创建一个Thradelocal的对象,根据变量的类型,设置Threallocal的泛型类型
* (变量可以是任何类型,一般为基本类型,或包装类型)对象后设置返回这个变量初始化值的方法
* (也可以看为是声明一个属于每个线程的变量,初始化为0 */
public static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
//返回该线程局部变量初始化值
protected Integer initialValue() {
return 0;
};
};
//调用该方法,设置线程的局部变量累计+1,然后返回(先获取局部变量+1后再设置到局部变量里)
public Integer getNum(){
//获当前线程中局部变量值 然后+1
Integer count= threadLocal.get()+1;
//把+1后的count在设置给属于每个线程的变量
threadLocal.set(count);
//再次获取修改后的属于每个线程的变量
return threadLocal.get();
}
public void remove(ThreadLocal<Integer> threadLocal){
//删除线程中的私有变量
threadLocal.remove();
}
public class ThreadLocaDemo2 extends Thread {
private Res res;
//初始化线程对象需要Res对象,因为在线程对象中
//的run方法中要调用Res的方法,进而对Res中的某个
//方法实现多线程
public ThreadLocaDemo2(Res res) {
this.res = res;
}
@Override//run方法中调用Res中的getNum方法
public void run() {
//循环调用getNum方法(getNum方法每执行一次,对执行这个方法的线程
// 的局部变量+1,然后返回调用这个方法的线程的局部变量)
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + "---" + res.getNum());
}
}
public static void main(String[] args) {
//创建Res对象,将res对象传入ThreadLocaDemo2中,创建自定义线程类对象
Res res = new Res();
ThreadLocaDemo2 threadLocaDemo1 = new ThreadLocaDemo2(res);//线程1
ThreadLocaDemo2 threadLocaDemo2 = new ThreadLocaDemo2(res);//线程2
ThreadLocaDemo2 threadLocaDemo3 = new ThreadLocaDemo2(res);//线程3
//运行线程1.会自动运行run方法,循环调用res中的getNum方法,
// 获取到线程1的局部变量
threadLocaDemo1.start();
//运行线程2.会自动运行run方法,循环调用res中的getNum方法,
// 获取到线程2的局部变量
threadLocaDemo2.start();
/*查看运行结果,会发现,虽然线程1在运行后循环调用了getNum
* 最终把线程1的变量修改为了3,
* 当线程2在去运行,循环调用getNum时,局部变量还是原来的0开始
* 线程1的修改不会影响到线程2*/
}
}
二. InheritableThreadLocal
- 在某些时刻需要考虑ThreadLocal的传递问题,使用InheritableThreadLocal
- Thread类中包含 threadLocals 和 inheritableThreadLocals 两个变量,其中 inheritableThreadLocals 即主要存储可自动向子线程中传递的ThreadLocal.ThreadLocalMap
public class Thread implements Runnable {
//......(其他源码)
//当前线程的ThreadLocalMap,主要存储该线程自身的ThreadLocal
ThreadLocal.ThreadLocalMap threadLocals = null;
//InheritableThreadLocal,自父线程集成而来的ThreadLocalMap,
//主要用于父子线程间ThreadLocal变量的传递
//本文主要讨论的就是这个ThreadLocalMap
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//......(其他源码)
}
- InheritableThreadLocal类重写了ThreadLocal的3个方法
//该函数在父线程创建子线程,向子线程复制InheritableThreadLocal变量时使用
protected T childValue(T parentValue) {
return parentValue;
}
//由于重写了getMap,操作InheritableThreadLocal时,
//将只影响Thread类中的inheritableThreadLocals变量,
//与threadLocals变量不再有关系
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
//类似于getMap,操作InheritableThreadLocal时,
//将只影响Thread类中的inheritableThreadLocals变量,
//与threadLocals变量不再有关系
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
- Thread初始化可以看到,采用默认方式产生子线程时,inheritThreadLocals=true;若此时父线程inheritableThreadLocals不为空,则将父线程inheritableThreadLocals传递至子线程
//默认情况下,设置inheritThreadLocals可传递
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
init(g, target, name, stackSize, null, true);
}
//初始化一个线程.此函数有两处调用,
//1、上面的 init(),不传AccessControlContext,inheritThreadLocals=true
//2、传递AccessControlContext,inheritThreadLocals=false
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//......(其他代码)
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
//......(其他代码)
}
- ThreadLocal.createInheritedMap中的createInheritedMap
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
//构建一个包含所有parentMap中Inheritable ThreadLocals的ThreadLocalMap
//该函数只被 createInheritedMap() 调用.
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
// ThreadLocalMap 使用 Entry[] table 存储ThreadLocal
table = new Entry[len];
// 逐一复制 parentMap 的记录
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
// 可能会有同学好奇此处为何使用childValue,而不是直接赋值,
// 毕竟childValue内部也是直接将e.value返回;
// 个人理解,主要为了减轻阅读代码的难度
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
- InheritableThreadLocal主要用于子线程创建时,需要自动继承父线程的ThreadLocal变量,方便必要信息的进一步传递
三. TransmittableThreadLocal
- TransmittableThreadLocal 是Alibaba开源的、用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展。若希望 TransmittableThreadLocal 在线程池与主线程间传递,需配合 TtlRunnable 和 TtlCallable 使用。