ThreadLocal
每个线程都有一个 ThreadLocal
变量的副本,实现了线程的隔离。
使用
//构建 ThreadLocal 并制定初始值。
public ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<Integer>(){
//重写此方法来给一个默认值。
@Override
protected Integer initialValue() {
return 1;
}
};
//构建 ThreadLocal 并制定初始值。
public ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<Integer>();
实现 runnable
接口来创建一个线程类,并在 run()
中打印 threadlocal
加线程 id 的值
/**
* 创建线程
*/
public class TestThread implements Runnable {
int id;
public TestThread(int id){
this.id = id;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "启动线程");
Integer s = 1 + id;
integerThreadLocal.set(s);
System.out.println(Thread.currentThread().getName() + ":" + integerThreadLocal.get());
//如果不调用 remove 会发生内存泄露
integerThreadLocal.remove();
}
}
利用一个方法来启动三个线程
/**
* 启动线程
*/
public void startThreadArray(){
Thread[] threads = new Thread[3];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(new TestThread(i));
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
}
进行测试
public static void main(String[] args) {
// write your code here
Main m = new Main();
m.startThreadArray();
}
}
输出结果:
Thread-0启动线程
Thread-1启动线程
Thread-2启动线程
Thread-0:1
Thread-2:3
Thread-1:2
分析
我们 Main
类有一个变量 integerThreadLocal
, 这个变量在三个线程中修改。正常情况下会出现线程安全问题。但是看结果是没有的。
这个时候,我们需要看 threadLoacl
的set
方法
integerThreadLocal.set(s);
我们看一下源码
ThreadLocal
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
, 然后调用了它的get
方法,我们看一下get
方法
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看出,它又调用了Thread
的threadLocals
.
可以看出在Thread
中有一个ThreadLocalMap
的成员变量
ThreadLocal.ThreadLocalMap threadLocals = null;
所以我们在 ThreadLocal
中的set
方法其实也就是调用的Thread
中的threadLocals
,createMap
也是给线程的
threadLocals进行初始化。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
然后再map.set(this, value)
中,将 ThreadLocal
为键,传入的 value
为值,将其保存在了线程中的 threadLocals 里。所以其实每个线程都有一个 ThreadLocal
对应的 value
,实现了线程隔离。
同理,ThreadLocal
的 get
方法也是取Thread
中的 threadLocals
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();
}
ThreadLocal 引发的内存泄露

上面这张图详细的揭示了ThreadLocal和Thread以及ThreadLocalMap三者的关系。
1、Thread中有一个map,就是ThreadLocalMap
2、ThreadLocalMap的key是ThreadLocal,值是我们自己设定的。
3、ThreadLocal是一个弱引用,当为null或者触发GC时,会被当成垃圾回收
4、突然我们ThreadLocal要被垃圾回收器回收了,但是此时我们的ThreadLocalMap生命周期和Thread的一样,它不会回收,这时候就出现了一个现象。那就是ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏。
解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存泄露的情况。
ThreadLocalMap 的开放寻址法
后面补~
代码
https://github.com/ios-yifan/threadlocal
参考
https://baijiahao.baidu.com/s?id=1653790035315010634&wfr=spider&for=pc
https://www.jianshu.com/p/3c5d7f09dfbd
网友评论