ThreadLocal对象的使用
一、引言
先看两段代码,看看执行结果有什么不同
第一段
public class ThreadLocalTest2 implements Runnable{
private static Integer a = 0;
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
++a;
System.out.println(Thread.currentThread().getName() + ":" + a);
}
}
public static void main(String[] args) {
Runnable runnable = new ThreadLocalTest2();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
Thread t3 = new Thread(runnable);
t1.start();
t2.start();
t3.start();
}
}
执行结果是
第二段
public class ThreadLocalTest implements Runnable{
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;
}
};
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
threadLocal.set(threadLocal.get() + 1);
Integer val = threadLocal.get();
System.out.println(Thread.currentThread().getName() + ":" + val);
}
threadLocal.remove();
}
public static void main(String[] args) {
Runnable runnable = new ThreadLocalTest();
Thread t1 = new Thread(runnable);
Thread t2 = new Thread(runnable);
Thread t3 = new Thread(runnable);
t1.start();
t2.start();
t3.start();
}
}
执行结果是
在第一段代码中,三个线程操作的变量是同一个,所以最后结果会累加到15;
而在第二段代码中,最后结果为三个线程最后分别输出5,说明操作的变量不是同一个,那么ThreadLocal到底是什么呢?它又是如何实现类似局部变量操作的呢?
二、简介
ThreadLocal 是Java中的一个线程局部变量。当使用 ThreadLocal 维护变量时,ThreadLocal 为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程。
三、原理
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();
}
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 getMap(Thread t) {
return t.threadLocals;
}
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private Entry[] table;
}
原理概述:
每个线程都有一个 ThreadLocalMap(ThreadLocal内部类),Map 中元素的键为 ThreadLocal对象,而值对应线程的变量副本,所以多线程对同一个ThreadLocal对象进行get和set操作的是对应线程的变量副本,不会有任何线程不安全的问题。
调用流程:
调用 threadLocal.set() ——> 调用getMap(Thread) ——> 返回当前线程的 ThreadLocalMap < ThreadLocal, value> ——> map.set(this, value),this 是 ThreadLocal
调用 threadLocal.get() ——> 调用getMap(Thread) ——> 返回当前线程的 ThreadLocalMap < ThreadLocal, value> ——> map.getEntry(this),返回value。
四、使用
-
Object get():获取该线程局部变量的值。
-
void set(Object value):给该线程局部变量赋值。
-
protected Object initialValue():返回该线程局部变量的初始值,可以重写该方法来设置初始值。
-
public void remove():将当前线程局部变量的值删除。
简单使用如下:
public class MyThread implements Runnable { private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() { @Override protected Integer initialValue() { //设置初始值为0 return 0; } }; @Override public void run() { // 设置变量值 threadLocal.set((int) (Math.random() * 100)); // 获取变量值 int value = threadLocal.get(); System.out.println(Thread.currentThread().getName() + " 的变量值为:" + value); // 清除变量值 threadLocal.remove(); } public static void main(String[] args) { MyThread myThread = new MyThread(); // 创建多个线程 Thread thread1 = new Thread(myThread, "线程1"); Thread thread2 = new Thread(myThread, "线程2"); Thread thread3 = new Thread(myThread, "线程3"); // 启动线程 thread1.start(); thread2.start(); thread3.start(); } }
结果如下:
五、内存泄漏
ThreadLocal内部维护了一个Entry数组,它的长度默认为16。每个Entry对象都包含了一个弱引用(ThreadLocal对象)和一个强引用(值对象)。当使用ThreadLocal的set方法设置值时,实际上是通过ThreadLocal对象的hashCode()方法计算出一个索引位置,然后将值存储在对应的Entry对象中。当使用ThreadLocal的get方法获取值时,也是通过ThreadLocal对象的hashCode()方法计算出索引位置,并从对应的Entry对象中取出值。
当线程结束后,如果没有手动调用ThreadLocal的remove()方法来清理ThreadLocal中的值,那么这些值将会一直存在于Entry数组中,而且由于Entry对象中的ThreadLocal对象是弱引用,所以这些ThreadLocal对象可能会被垃圾回收器回收,但对应的值对象却无法被回收。这就导致了内存泄漏的问题,即ThreadLocal中的值无法被释放,占用着内存资源。
解决方法为:
-
在使用ThreadLocal的地方,及时调用remove()方法来清理ThreadLocal中的值,以确保值对象能够被回收。
//使用完成之后,进行remove()清除 threadLocal.remove();
-
使用弱引用来存储ThreadLocal中的值,当线程结束后,ThreadLocal对象被垃圾回收器回收时,对应的值对象也会被回收。
// 使用弱引用来存储值 private static ThreadLocal<WeakReference<Integer>> threadLocal = new ThreadLocal<>();