理解
线程的本地变量,赋值之后,多个线程之间的值不可见。 结合线程池使用时线程结束时要清除本次使用的本地变量。
整体:每个线程内部维护了一个ThreadLocal.ThreadLocalMap
的变量threadLocals
(key:ThreadLocal,value),ThreadLocal赋值取值时操作的其实都是该变量,该变量是每个线程的私有变量,必然不同线程之间不可见。

定义初始化
ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
threadLocal.set()方法
set方法中,首先获取当前线程ThreadCurrent.currentThread()
,然后getMap(t)
获取获取线程的ThreadLocal.ThreadLocalMap
变量,不为空,set
值的时候,key
为当前线程的TheadLocal
实例,value
为传入的value
值,为空创建一个空的ThreadCurrent.currentThread()
并赋值。



threadLocal.get()方法
注意没有传入的参数,因为key默认是当前线程的TheadLocal
实例


可以看到最后返回了一个方法,该方法就是没找到就顺便这次get的时候插入一个空值。
initialValue()
返回null
threadLocal.remove() 方法
从当前线程中移除以当前ThreadLocal的实例为key的值

应用场景
ThreadLocal是用在多线程的场景的
1.保存线程上下文信息,在任意需要的地方可以获取
2.线程安全的,避免某些情况需要考虑线程安全必须同步带来的性能损失
1.
保存线程上下文信息一般用在优秀的框架里面:比如Spring的事务管理,用ThreadLocal存储Connection,从而各个DAO可以获取同一Connection,可以进行事务回滚,提交等操作。
2.

由于不需要共享信息,自然就不存在竞争问题了,从而保证了某些情况下线程的安全,以及避免了某些情况需要考虑线程安全必须同步带来的性能损失!!!
ThreadLocal提供了线程安全的另一种思路,我们平常说的线程安全主要是保证共享数据的并发访问问题,通过sychronized锁等保证数据的一致性。ThreadLocal是让每个线程都拥有一份线程私有的数据,线程之间彼此不影响。
例子:
public class ThreadLocalTest {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(() -> {
try {
for (int i = 0; i < 100; i++) {
threadLocal.set(i);
System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
threadLocal.remove();
}
}, "threadLocal1").start();
new Thread(() -> {
try {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "====" + threadLocal.get());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
threadLocal.remove();
}
}, "threadLocal2").start();
}
}

threadLocal1进行set值对threadLocal2并没有任何影响!
再看一个例子
public class ThreadLocalDemo {
private static ThreadLocal<Integer> initVal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0;//重写上面默认返回null的函数
}
};
public static void modify() {
initVal.set(initVal.get() + 10);
System.out.println(initVal.get());
}
public static void main(String[] args) {
new Thread("Thread#1") {
@Override
public void run() {
modify();
}
}.start();
new Thread("Thread#2") {
@Override
public void run() {
modify();
}
}.start();
}
}
产生副本图

回收问题
ThreadLocal被垃圾回收后,在ThreadLocalMap里对应的Entry的键值会变成null,但是Entry是强引用,那么Entry里面存储的Object,并没有办法进行回收,所以ThreadLocalMap 做了一些额外的回收工作。就是需要调用
threadLocal.remove();
且最好在finally
下调用。
ThreadLocalMap中的Key是ThreadLocal实例对象
ThreadLocalMap中的key是ThreadLocal实例对象,并不是想当然的线程ID等,用线程ID唯一值的话再一个线程有多个ThreadLocal变量时就不知道应该获取哪个Value
ThreadLocal用static修饰
static修饰ThreadLocal的话,使之成为静态变量而不是实例变量,避免了重复创建ThreadLocal实例,因为ThreadLocal本身就是线程的副本
参考文章
https://juejin.im/post/5d707e4fe51d4561e43a6d0d
https://juejin.im/post/5d6c730051882566ce35a887
网友评论