《Java并发编程之美》读书笔记
ThreadLocal
多线程在访问同一个共享变量的时候容易出现并发的问题,特别是在多个线程对同一个共享变量进行写入的时候,一般都要对共享变量进行适当的同步。
同步的措施一般都是加锁,这就需要使用者对锁有一定的了解,这显然增加了使用者的负担,那么有没有一种方式可以做到,当创建一个变量后,每个线程对其进行访问的时候访问的是自己线程的变量呢->ThreadLocal
ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal,那么访问这个变量的每个线程都会有这和个变量本地的一个副本。当多个线程操作这个ThreadLocal变量时,实际上是在操作自己本地内存里面的变量,从而避免了线程安全问题。创建了一个ThreadLocal变量后,每个线程都会复制一个变量复制到自己的本地内存
ThreadLocal使用示例
public class ThreadLocalTest {
static void print(String str){
//获取到当前线程本地内存中的localVariable值
System.out.println(str+":"+localVariable.get());
//删除当前线程本地内存中的localVariable值
localVariable.remove();
}
//创建ThreadLocal变量
static ThreadLocal localVariable=new ThreadLocal<>();
public static void main(String[] args) {
Thread threadOne=new Thread(new Runnable() {
@Override
public void run() {
//设置当前线程本地内存中的localVariable值
localVariable.set("threadOne local variable");
print("threadOne");
System.out.println("threadOne remove after"+":"+localVariable.get());
}
});
Thread threadTwo=new Thread(new Runnable() {
@Override
public void run() {
localVariable.set("threadTwo local variable");
print("threadTwo");
System.out.println("threadTwo remove after"+":"+localVariable.get());
}
});
threadOne.start();
threadTwo.start();
}
}
本例子开启了两个线程,在每个线程内部设置了本地变量的值,然后调用print函数打印当前本地变量的值,如果打印后调用了本地变量的remove方法之后,则会删除本地内存中的共享变量。
线程One run方法通过设置localvariable的值,这其实是设置的是线程one本地内存中的一个副本,这个副本线程two是访问不了的。
ThreadLocal实现原理
Thread类内部会有一个threadLocals和inheritableThreadLocals,他们都是ThreadLocalMap类型的变量,而ThreadLocalMap是一个定制化的hashMap,在默认的情况下,每个线程中的两个变量都为null,只有当前线程第一次调用ThreadLocal的set或者get方法之后才会创建他们,其实每个线程的本地变量并不是存在ThreadLocal实例里面,而是存放在调用线程的threadLocals里面,也就是说ThreadLocal类型的本地变量存放在具体的线程的内存空间中,ThreadLocal就是一个空壳,它通过set方法把value值放入线程的threadlocals里面存放起来,当调用线程使用它的get方法时,再从当前线程的threadlocals变量里面将其拿出来,如果调用线程一直不终止,那么这个本地变量会一直存放在调用线程的threadlocals里面,所以当不需要使用本地变量的时候,可以通过调用ThreadLocal变量里面的remove方法,从当前线程的threadlocals里面删除该本地变量
另外 Thread里面的threadlocals为什么设置为map结构?很明显是因为多个线程可以关联多个ThreadLocal变量。
简单分析ThreadLocal的set,get以及remove方法的是实现逻辑
- public void set(T value)
public void set(T value) {
//获取到当前线程
Thread t = Thread.currentThread();
//将当前线程作为key去找对应的线程变量,找到则设置
ThreadLocalMap map = getMap(t);
if (map != null) {
map.set(this, value);
} else {
//如果是第一次调用就创建当前线程对应的HashMap
createMap(t, value);
}
}
代码中首先获取调用线程,然后使用当前线程作为参数调用getMap(t)方法
可以看到,getMap(t)的作用是获取线程自己的变量threadlocals,threadlocals变量被绑定到了线程的成员变量上。
如果getMap(t)返回值不为空,则把value值设置到threadLocals中,也就是把变量值放入到当前线程的内存变量threadLocals中。threadLocals是一个HashMap结构,其中的key就是当前ThreadLocal的实例对象的引用,value是通过set方法传递的值
如果getMap(t)返回空值则说明是第一次调用set方法,这时用 createMap(t, value)创建当前线程的threadLocals变量。
createMap创建当前线程的threadLocals变量。
- T get()
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的threadLocals变量 是一个ThreadLocalMap结构
ThreadLocalMap map = getMap(t);
//如果threadLocals不为空,则返回对应本地变量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//threadLocals为空的话就初始化当前线程的threadLocals变量
return setInitialValue();
}
上诉代码首先获取当前线程的实例,在获取当前线程的threadLocals变量,如果不为null则直接返回当前线程绑定的本地变量,否则执行代码初始化。
private T setInitialValue() {
//初始化为null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//如果当前线程的threadLocals变量不为空
if (map != null) {
//value为null
map.set(this, value);
} else {
//如果当前线程的threadLocals变量为空
createMap(t, value);
}
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal>) this);
}
return value;
}
如果当前线程的threadLocals变量不为空,则设置为当前线程的本地变量值为null,否则调用createMap(t, value)方法创建当前线程的threadLocals变量。
-
void remove()
如果当前线程的threadLocals变量不为空,则删除当前线程中指定ThreadLocal实例即this的本地变量。
总结
在每个线程内部都有一个名为threadLocals的成员变量,这个变量的类型是HashMap,其中key就为我们定义的ThreadLocal类型的变量的this引用,value则为我们用set方法设置的值,每个线程的本地变量存放在线程自己的内存变量threadLocals里面,如果当前线程一直不消亡,那么这些本地变量就会一直存在,所以可能会造成内存溢出,因此使用完毕后记得调用ThreadLocal的remove方法删除对应线程的threadLocals中的本地变量。
注:在JUC包里面的ThreadLocalRandom,就是借鉴这中思想实现的。
ThreadLocal不支持继承性
public class ThreadLocalDemo {
//创建线程变量
private static ThreadLocal threadLocal=new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("helloworld");
Thread thread=new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread:"+threadLocal.get());
}
});
thread.start();
System.out.println("main:"+threadLocal.get());
}
}
也就是说,同一个ThreadLocal变量在父线程中设置值后,在子线程中是获取不到的,如之前所说,这是很正常的现象,因为子线程thread里面调用get方法时当前线程为thread线程,而这里调用set方法设置线程变量的是main线程,两者是不同的线程所以子线程访问时为null;
Inheritable ThreadLocal类
为了解决ThreadLocal类不支持继承性的这个问题,InheritableThreadLocal应运而生,它继承自ThreadLocal,提供了一个属性,就是让子线程可以访问可以访问在父线程中设置的本地变量,
InheritableThreadLocal源代码:
public class InheritableThreadLocal extends ThreadLocal {
protected T childValue(T parentValue) {
return parentValue;
}
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}
由以上代码可知,InheritableThreadLocal继承ThreadLocal类,并且重写了三个方法。重写了createMap方法,所以现在第一次调用set方法的时候,创建的是当前线程的t.inheritableThreadLocals变量的实例而不再是threadLocals实例。当调用get方法获取当前线程内部的map变量时,获取的是t.inheritableThreadLocals而不再是threadLocals。
综上,在InheritableThreadLocal世界里,变量由inheritableThreadLocals代替了threadLocals
观察如何让子线程可以访问父线程的本地变量。
public Thread(Runnable target) {
this(null, target, "Thread-" + nextThreadNum(), 0);
}
private Thread(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//获取当前线程
Thread parent = currentThread();
//如果父线程的inheritThreadLocals不为null
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
//设置子线程中的inheritThreadLocals变量
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
this.stackSize = stackSize;
this.tid = nextThreadID();
}
如上的代码在创建的过程中,在构造函数中会调用私有的构造方法,先获取当前的线程,这里是main函数所在的线程,也就是main线程,然后再判断main函数所在的线程里面inheritableThreadLocals是否为空,然后就会执行createInheritedMap方法
可以看到createInheritedMap的内部使用的是父线程的inheritableThreadLocals变量作为构造函数创建了一个新的ThreadLocalMap变量。
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
for (Entry e : parentTable) {
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal
在该构造函数的内部将父线程的inheritableThreadLocals成员变量复制到新的ThreadLocalMap变量当中
总结,InheritableThreadLocal通过重写ThreadLocal类的代码让本地变量保存到了具体的inheritableThreadLocals里面,那么线程在通过InheritableThreadLocal实例的set或者get方法设置变量时,就会创建当前线程的inheritableThreadLocals变量。当父线程创建子线程时,构造函数会把父线程中的inheritableThreadLocals变量里面的本地变量复制一份保存到子线程的inheritableThreadLocals里面
把之前的代码改为:
private static ThreadLocal threadLocal=new InheritableThreadLocal<>();
可见,现在可以从子线程正常获取到线程变量的值了。
在什么情况下需要子线程可以获取到父线程的thredLocal变量呢?
比如子线程需要使用存放在threadLocal变量中的用户信息,再比如说一些中间件需要把统一的id追踪的整个调用链路记录下来。其实子线程使用父线程中的threadLocal方法有很多种,比如创建线程时候,使用父线程中的变量,并将其复制到子线程中,或者在父线程中构造一个map作为参数传递给子线程。但是这些方法都改变了我们的使用习惯,所以在这些情况下InheritableThreadLocal就显得比较有用。
参考资料:
《Java并发编程之美》