目录
2.1 Thread类的两个ThreadLocalMap类型的参数
3.2 支持继承的InheritableThreadLocal
1.概述
- ThreadLocal是一个本地线程副本变量工具类
前面的博文中有讲到过,引发线程安全的原因主要是
- 1.存在共享资源
- 2.存在多个线程去操作同一个共享资源
示例如下:
package ThreadLocal;
class ThreadLocalDemo{
//共享资源
private int count = 20;
//写操作
public int decrement(){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
return count;
}
public static void main(String[] args) {
ThreadLocalDemo demo = new ThreadLocalDemo();
//线程A共享资源
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + demo.decrement());
}
},"A").start();
//线程B共享资源
new Thread(()->{
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + demo.decrement());
}
},"B").start();
}
}
而我们前面的思路是通过sychronized锁或者CAS无锁策略来控制多个线程的执行顺序来保证数据的一致性,即我们前面的思路都是从引发线程的第二个原因入手的。
那么我们能否从第一个原因入手呢?既然是共享资源引发的问题,我们能不能让它不是共享资源呢?ThreadLocal提供了线程安全的另一种思路,即:
- 通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而ThreadLocal让各个线程都拥有一份线程私有的数据,让每个线程绑定自己的值,线程在操作数据的时候,仅仅是操作自己线程内部的变量,这样线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用。
如果你创建了一个ThreadLocal
变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal
变量名的由来。他们可以使用 get()
和 set()
方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。
案例:
package ThreadLocal;
class ThreadLocalDemo{
private ThreadLocal<Integer> count = new ThreadLocal<Integer>(){
//重写ThreadLocal的初始化方法,用于初始化ThreadLocal变量
@Override
protected Integer initialValue() {
return 0;
}
};
public int getNext(){
Integer value = count.get();
value++;
count.set(value);
return value;
}
public static void main(String[] args) {
ThreadLocalDemo demo = new ThreadLocalDemo();
new Thread(()->{
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + demo.getNext());
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + demo.getNext());
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "\t" + demo.getNext());
}
},"C").start();
}
}
2.图解+源码分析ThreadLocal原理
以下分析结合上图去理解:
我们先从上往下去分析:
2.1 Thread类的两个ThreadLocalMap类型的参数
- 1.每个线程Thread都有两个自己的属性——threadlocals和inheritableThreadLocals,它是ThreadLocalMap类型的,在Thread类中源码如下:
- 而从源码也可以看出ThreadLocalMap是ThreadLocal的内部类,我们可以把
ThreadLocalMap
理解为ThreadLocal
类实现的定制化的HashMap
。 - 默认情况下这两个变量都是null,只有当前线程调用
ThreadLocal
类的set
或get
方法时才创建它们,实际上调用这两个方法的时候,我们调用的是ThreadLocalMap
类对应的get()
、set()
方法。
2.2 ThreadLocalMap详解
- 2.我们先来看一看ThreadLocalMap这个内部类
(1)成员变量与内部类
static class ThreadLocalMap {
/**
* 定义了数组中存储的对象——键值对 键: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;
}
}
/**
* 初始化数组的容量
*/
private static final int INITIAL_CAPACITY = 16;
/**
* 存放Entry的数组
*/
private Entry[] table;
/**
* 数组中存放的Entry的个数
*/
private int size = 0;
/**
* 扩容阈值
*
* 默认为0
*/
private int threshold; // Default to 0
}
- 可以发现ThreadLocalMap的底层存放的是数组,而该数组中存放的元素是键值对,即Entry对象
- 而Entry是ThreadLocalMap中定义的静态内部类,它继承自WeakReference,即弱引用
- 这时,会奇怪在Entry中没有看到有定义key字段呢?
- 其实可以看到在Entry的构造方法中,调用了super(k),即调用了WeakReference的构造方法
- 同理,WeakReference又调用了Reference的构造方法
- 可以发现在Reference的构造方法中,最终将k赋值给了Reference中定义的字段referent
- 所以最终ThreadLocalMap中的key是referent字段,由于它是从WeakRefere