一、ThreadLocal概念
ThreadLocal是线程变量,在每个线程中都创建了一个ThreadLocalMap对象,存储当前ThreadLocal副本,该Map是以ThreadLocal对象为key,任意对象为值的存储结构,每个线程都能够访问属于自己的ThreadLocalMap存储的value。而ThreadLocal能够集中管理和操作这些Map。
类中定义的ThreadLocal实例一般会被private static修饰,这样可以让ThreadLocal实例的状态和Thread绑定在一起
二、ThreadLocalMap
没看源码之前我以为ThreadLocalMap是实现了Map接口,但是源码中的ThreadLocalMap是ThreadLocal的内部类,内部的Entry也是独立实现,在ThreadLocalMap中,也是用Entry来保存K-V结构数据的。但是Entry中key只能是ThreadLocal对象,value可以是其他任何对象
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
-
Thread中的threadLocals变量指向的是一个map,这个map就是
ThreadLocal.ThreadLocalMap
,里面存放的是跟当前线程绑定的ThreadLocal变量。同样还有一个ThreadLocal.ThreadLocalMap
类型的变量——inheritableThreadLocals里面也是存放的ThreadLocal变量,但是存放的是从当前线程的父线程继承过来的ThreadLocal变量。 -
既然是Map那么肯定要解决Hash冲突,由于ThreadLocalMap结构简单,没有next引用,即她没有使用链表的方式来解决冲突,而是使用线性探测的方式,根据初始的key的hashCode确定元素在table中的位置,如果发生冲突即该位置已经被占用,且是其他key值,那么寻找一定步长的下一个位置,直到能够找到存放的位置。ThreadLocalMap采用的步长就是1,加一或者减一。
/**
* Increment i modulo len.
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}
/**
* Decrement i modulo len.
*/
private static int prevIndex(int i, int len) {
return ((i - 1 >= 0) ? i - 1 : len - 1);
}
三、ThreadLocal中的方法
3.1、ThreadLocal是如何被线程使用的
线程中的栈上存放的是Thread引用和ThreadLocal引用,Thread引用会引用一个ThreadLocalMap对象
ThreadLocal中有几个核心方法:
public T get()
public void set(T value)
public void remove()
这几个方法都是对静态内部类ThreadLocalMap进行操作,根据当前线程来获取map,对map进行操作,比如set():
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
set方法先获取当前线程,然后创建一个ThreadLocalMap对象并接受当前线程中的map,判断是否为空,不为空则覆盖之前的值,为空则重新创建一个ThreadLocalMap并绑定到当前线程的threadLocals:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
其他方法与之类似,比较容易懂,可以自行查看源码。
四、ThreadLocal使用举例
/**
* @Description:
* @Author: Mt.Li
* @Create: 2020-07-23 21:11
*/
public class ThreadLocalDemo {
public static class MyRunnable implements Runnable {
// jdk1.8之后提供的新的初始化方法
// ThreadLocal<Integer> threadLocal1 = ThreadLocal.withInitial(() -> 0);
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
DataTest dataTest = new DataTest();
@Override
public void run() {
threadLocal.set(dataTest.num);
threadLocal.set(threadLocal.get() + 1);
System.out.println("dataTest中的num值为:" + dataTest.num);
System.out.println(Thread.currentThread().getName() + "中的ThreadLocal's num为:" + threadLocal.get());
}
}
private static class DataTest{
private Integer num = 0;
}
public static void main(String[] args) throws InterruptedException {
MyRunnable threadLocalDemo = new MyRunnable();
Thread thread1 = new Thread(threadLocalDemo, "thread 1");
Thread thread2 = new Thread(threadLocalDemo, "thread 2");
Thread thread3 = new Thread(threadLocalDemo, "thread 3");
thread1.start();
thread2.start();
thread3.start();
thread1.join();
thread2.join();
thread3.join();
}
}
运行结果:
dataTest中的num值为:0
thread 1中的ThreadLocal's num为:1
dataTest中的num值为:0
thread 2中的ThreadLocal's num为:1
dataTest中的num值为:0
thread 3中的ThreadLocal's num为:1
可以看到,线程与线程之间互不影响,只对属于自己的value进行操作,对传入的用来初始话的对象或者变量,是不会进行改变的。
以上全为个人理解,如有不对,请及时指出