1. ThreadLocal
2. ThreadLocal使用示例
3. ThreadLocal源码分析
4. 线程池使用ThreadLocal导致的内存泄露案例解析
ThreadLock是JDK包提供的,它提供了线程本地变量,也就是如果创建了一个ThreadLocal变量,访问这个变量的每个线程都会有这个变量的一个本地副本。
当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。创建一个ThreadLock变量后,每个线程都会复制一个变量到自己的本地内存
ThreadLocal
实例通常是类中的private static字段,它们希望将状态与某一个线程(例如,用户ID或事务ID)相关联。
除了这四个方法,ThreadLocal内部还有一个静态内部类ThreadLocalMap,该内部类才是实现线程隔离机制的关键,get()、set()、remove()都是基于该内部类操作。ThreadLocalMap提供了一种用键值对方式存储每一个线程的变量副本的方法,key为当前ThreadLocal对象,value则是对应线程的变量副本。
public class ThreadLocalTest {
// print函数
static void print(String str) {
// 1.1 打印当前线程本地内存中的localVariable变量的值
System.out.println(str + ":" + localVariable.get());
// 1.2 清除当前线程本地内存中的localVariable变量
// localVariable.remove();
}
// 创建ThreadLocal变量
private static ThreadLocal<String> localVariable = new ThreadLocal<>();
public static void main(String[] args) {
// 创建线程one
Thread threadOne = new Thread(new Runnable() {
@Override
public void run() {
// 设置线程one中本地变量的localVariable的值
localVariable.set("threadOne local variable");
// 调用打印函数
print("threadOne");
// 打印本地变量值
System.out.println("threadOne remove after" + ":" + localVariable.get());
}
});
// 创建线程two
Thread threadTwo = new Thread(new Runnable() {
@Override
public void run() {
// 设置线程two中本地变量的localVariable的值
localVariable.set("threadTwo local variable");
// 调用打印函数
print("threadOne");
// 打印本地变量值
System.out.println("threadTwo remove after" + ":" + localVariable.get());
}
});
// 启动线程
threadOne.start();
threadTwo.start();
}
}
ThreadLocal只是一个工具类,具体存放变量的是线程的threadLocals变量。threadLocals是一个ThreadLocalMap类型的变量
由图可知,ThreadLocal内部是一个Entry数组,Entry继承自WeakReference,Entry内部的value用来存放通过ThreadLocal的set方法传递的值,ThreadLocal存放ThreadLocalMap的key中,如下所示
1. localVariable.set("threadOne local variable"); // 跟踪set(在ThredLocal中)方法
2. public void set(T value) {
Thread t = Thread.currentThread();
// 得到的是当前线程里面的变量ThreadLocalMap,存放key(ThreadLocal),value(val)的键值对
ThreadLocalMap map = getMap(t);
if (map != null)
// 设置map键值对,this->ThreadLocal
map.set(this, value); // 继续跟踪
else
createMap(t, value);
}
3. private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table; // 计算当前ThreadLocal变量所在的table数组,尝试使用快速定位方法
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; // 使用循环防止快速定位失效,遍历table数组
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value); // 继续跟踪这个~~!
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
4. static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k); // 继续跟踪到父类构造方法
value = v;
}
}
5. public WeakReference(T referent) {
super(referent);
}
6. Reference(T referent) {
this(referent, null);
}
k被传递给WeakReference的构造函数,也就是说ThreadLocalMap里面的key为ThreadLocal对象的弱引用,具体就是referent变量引用了ThreadLocal对象,value为具体调用ThreadLocal的set方法时传递的值
public class ThreadPoolTest {
static class LocalVariable{
private Long[] a = new Long[1024*1024];
}
// 核心线程数:5 最大线程数:5
final static ThreadPoolExecutor poolExcutor = new ThreadPoolExecutor(5,5,1, TimeUnit.MINUTES,new LinkedBlockingQueue<>());
final static ThreadLocal<LocalVariable> localVariable = new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 50; i++) {
poolExcutor.execute(new Runnable() {
@Override
public void run() {
localVariable.set(new LocalVariable());
System.out.println("use local variable");
}
});
Thread.sleep(1000);
}
System.out.println("pool execute over");
}
}
使用localVariable.remove()方法后
对于两图,可知代码一发生了内存泄露
原因: 第一次运行代码时,在设置线程的localVariable变量后没有调用localVariable.remove()方法,导致线程池的5个核心线程的threadLocals变量里面的new LocalVariable()实例没有被释放。虽然线程池里面的任务执行完,但是线程池里面的5个线程会一直存在知道JVM进程被杀死。
第二次运行代码时,及时调用了localVariable.remove()方法进行了清除,所以不会存在内存泄露