上一节你应该学习了thread的基本知识和源码原理,相熟了线程的利用场景。这一节来学习下和Thread相干的一个类,ThreadLocal。
什么是ThreadLocal?
<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”>什么是ThreadLocal?</span></h3></div>
字面意思是线程本地变量的意思。用一句话解释就是:线程本地的变量正本,属于每个线程本人独有的。
为什么说是变量正本呢?因为每个线程应用ThreadLocal设置本人的值,设置的值相互之间不受影响,然而应用的是同一个ThreadLocal对象。所以设置的每个变量,是给每个线程一个独有的变量正本。
你能够画一个图来了解下:
当你晓得了什么是ThreadLocal后,让咱们简略来应用一下它,看下他的应用成果。
Hello ThreadLocal
<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> Hello ThreadLocal</span></h3></div>
上面通过一段Hello ThreadLocal小程序,让你回顾下ThreadLocal的应用。假如有这么一个场景:
线程启动了2个线程,应用threadLocal设置了一个Loan对象,main线程也设置了本人的loan对象。线程2和main线程在设置前尝试拜访threadLocal中的数据。
代码实现如下:
public class HelloThreadLocal {
private static ThreadLocal<Loan> threadLocal = new ThreadLocal<Loan>();
public static void main(String[] args) {
//线程1 应用threadLocal设置本人的变量正本
new Thread(() -> {
threadLocal.set(new Loan("zhangsan", "1000.00"));
System.out.println("线程-1loan:"+threadLocal.get());
}).start();
//线程2 应用threadLocal设置本人的变量正本
new Thread(() -> {
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
}
HelloThreadLocal.Loan loan = threadLocal.get();
System.out.println("线程-2loan:"+loan);
threadLocal.set(new Loan("lisi", "2000.00"));
loan = threadLocal.get();
System.out.println("线程-2loan:"+loan);
}).start();
try {
Thread.sleep(5 * 1000);
} catch (InterruptedException e) {
}
System.out.println("main-线程loan:"+threadLocal.get());
threadLocal.set(new Loan("wangwu", "1000.00"));
System.out.println("main-线程loan:"+threadLocal.get());
}
@Data
@AllArgsConstructor
public static class Loan {
private String name;
private String amount;
}
}
下面的代码输入后果如下:
线程-1loan:HelloThreadLocal.Loan(name=zhangsan, amount=1000.00)
线程-2loan:null
线程-2loan:HelloThreadLocal.Loan(name=lisi, amount=2000.00)
main-线程loan:null
main-线程loan:HelloThreadLocal.Loan(name=wangwu, amount=1000.00)
能够看出,每个线程无奈获取到其余线程设置的loan对象,哪怕是应用同一个ThreadLocal设置的。为什么会这样呢?其实就是因为每个线程的变量正本,ThreadLocal只是一个工具,操作了线程本地的变量正本而已。具体原理如下图所示:
ThreadLocal源码分析get办法脉络
<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> ThreadLocal源码分析get办法脉络</span></h3></div>
置信你通过下面的例子,曾经了解ThreadLocal的作用了。它的底层是如何做到的呢?你须要剖析一下它的源码了。
这里咱们把栗子简化下,能够更好的剖析get、set办法的源码。简化HelloThreadLocal代码如下:
public class ThreadLocalGetMethod {
private static ThreadLocal<Loan> threadLocal = new ThreadLocal<ThreadLocalGetMethod.Loan>();
public static void main(String[] args) {
//线程1
new Thread(() -> {
System.out.println("线程-1loan:"+threadLocal.get()); //输入null
}).start();
}
@Data
@AllArgsConstructor
public static class Loan {
private String name;
private String amount;
}
}
依照下面的例子,你先new了一个ThreadLocal对象,所以须要看下ThreadLocal构造函数,做了什么事件没有,很显著什么都没做。
public ThreadLocal() {
}
之后线程1会间接执行了threadLocal.get操作。让咱们看下他的源码:
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();
}
你能够看到,下面的get办法的脉络次要如下:
1. 获取以后线程的一个变量ThreadLocalMap
2. 如果map为空调用setInitialValue返回默认值,并创立map
3. 如果map非空获取entry中key的对应的value值
你能够先画一个图,之后再来别离看下每一步。threadLocal的get办法外围脉络如图所示:
ThreadLocal源码分析get办法细节
<div class=”output_wrapper” id=”output_wrapper_id” style=”width:fit-content;font-size: 16px; color: rgb(62, 62, 62); line-height: 1.6; word-spacing: 0px; letter-spacing: 0px; font-family: ‘Helvetica Neue’, Helvetica, ‘Hiragino Sans GB’, ‘Microsoft YaHei’, Arial, sans-serif;”><h3 id=”hdddd” style=”width:fit-content;line-height: inherit; margin: 1.5em 0px; font-weight: bold; font-size: 1.3em; margin-bottom: 2em; margin-right: 5px; padding: 8px 15px; letter-spacing: 2px; background-image: linear-gradient(to right bottom, rgb(43,48,70), rgb(43,48,70)); background-color: rgb(63, 81, 181); color: rgb(255, 255, 255); border-left: 10px solid rgb(255,204,0); border-radius: 5px; text-shadow: rgb(102, 102, 102) 1px 1px 1px; box-shadow: rgb(102, 102, 102) 1px 1px 2px;”><span style=”font-size: inherit; color: inherit; line-height: inherit; margin: 0px; padding: 0px;”> ## ThreadLocal源码分析get办法细节</span></h3></div>
这个是总的脉络图,接下来看一下每一步的细节。
1、获取以后线程的一个变量ThreadLocalMap。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//临时省略
}
这两句代码第一句是获取到以后运行的线程对象,第二句获取了如下map,能够从正文看进去,理论就是获取了thread对象t的一个属性,这属性是一个ThreadLocalMap。代码如下:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
不晓得各位还记得上一节的thread创立的场景么?当中有一些细节并没有讲,thread除了状态、名字、线程id以外,还有两个比拟要害的属性,threadLocals和inheritableThreadLocals。代码如下:
public class Thread implements Runnable {
//......(其余源码)
/*
* ThreadLocal应用,以后线程的ThreadLocalMap,次要存储该线程本身的ThreadLocal
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal应用,自父线程继承而来的ThreadLocalMap,
* 次要用于父子线程间ThreadLocal变量的传递
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//......(其余源码)
}
正文写的分明,一个用于是父子线程传递的变量正本Map,个别是InheritableThreadLocal才会应用,一个是本人线程变量正本Map个别ThreadLocal应用。
上一节还有一个细节我没有讲,inheritableThreadLocals这个变量在创立线程的时候调用init办法的时候会判断,如果父线程有值复制到子线程一份。代码如下:
/**
* 初始化一个线程.此函数有两处调用,
* 1、下面的 init(),不传AccessControlContext,inheritThreadLocals=true
* 2、传递AccessControlContext,inheritThreadLocals=false
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
//......(其余代码)
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
//......(其余代码)
}
inheritableThreadLocals这个变量的利用在申请跟踪,传递traceId的时候能够被用到。这里不做过多关注,外围还是关注threadLocals这个根本的线程变量正本。
你能够看下整个Map是什么?是一个ThreadLocalMap,它是ThreadLocal的外部类。所以你能够失去如下图所示论断:
回过头来再看下,这两行代码,十分要害的一点就是,尽管应用了ThreadLocal的get,然而操作的理论是以后线程的threadLocals本地变量正本的Map,这一点是很重要的。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//临时省略
}
getMap办法执行实现后,流程如下图所示:
2、如果map为空,调用setInitialValue返回默认值,并创立map
因为以后线程的获取到正本变量map为null,所以会执行到setInitialValue这个分支,如下所示:
所以你要来看下setInitialValue的代码:
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
你看完这个代码后,能够发现这个办法外围脉络做了两件事件,一个是初始化值,一个是创立map。
如下图所示:
首先下面第一句代码是调用了initialValue办法,从名字上看就是一个初始化的动作,能够看下它的源码,非常简单:
protected T initialValue() {
return null;
}
默认返回null,能够通过重写这个办法或者一个匿名外部类(jdk1.8)来设置一个初始值。
这里我给出大家设置初始值的形式。
应用重写initialValue形式
private static ThreadLocal<Loan> threadLocal = new ThreadLocal<Loan>() {
@Override public Loan initialValue() {
return new Loan("默认值", "1000.00");
}
};
或者应用withInitial,匿名外部类
private static ThreadLocal<Loan> threadLocal = ThreadLocal.withInitial(() -> new Loan("默认值", "1000.00"));
setInitialValue执行到这里,逻辑很简略,如下:
接着进行了if判断,以后线程的本地变量正本threadLocals通过getMap获取到的必定默认是null,所以会执行创立Map如下图:
所以会创立map,执行如下代码
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
下面这个创立map 的细节咱们不过来深刻了,ThreadLocalMap不是咱们要讲的重点,有趣味的同学能够看下他的源码,这里给出ThreadLocalMap外围点:
- 底层层是数组,默认大小16,默认扩容阈值10,扩容阈值计算形式:threshold = len * 2 / 3= 10
- key是threadLocal,vlaue是ThreadLocal的泛型对象
- 寻址算法firstKey.threadLocalHashCode & (INITIAL_CAPACITY – 1);
- hash值算法:firstKey.threadLocalHashCode,底层是用了AtomicInteger和一个增量值HASH_INCREMENT,保障
- Hash抵触应用凋谢寻址法(HashMap是单链表法)
- 凋谢寻址的外围是地位有元素了就换地位
这里value应该是为null,因为initialValue办法没有指定初始化值。
最初咱们回到get源码的脉络图。通过getMap()、setInitialValue()办法调用后,最终线程threadLocal.get()会输入null的值。如下所示:
好了,明天ThreadLocal就学习到这里,下一节咱们来摸索下:
- ThreadLocal的set源码原理
- JVM的中的强援用、弱援用、软援用、虚援用
- 弱援用在ThreadLocal的利用
- ThreadLocal内存透露问题剖析
-
ThreadLocal利用场景举例
本文由博客一文多发平台 OpenWrite 公布!
发表回复