在不少场景下,咱们想要传递参数必须经过显式参数定义,可是方法栈层次更加深的时候,显的特别不优雅,例如传递用户信息到调用的方法中:java
//controller传递用户信息到serviceA
controller.serviceA(user);
//继续向serviceB传递
serviceA.serviceB(user);
//最终传递到dao层
serviceB.dao(user);
复制代码
每个方法都要定义一个显式的用户参数,显得很是臃肿,有没有其余办法呢?数组
有人可能想到了定义一个公共的属性或者静态变量,可是这样会引起一个多线程共享变量线程不安全问题,因此必须对这个公共属性进行加锁控制。安全
一旦上锁,那效率可不是慢了一星半点,有没有更加高效的办法呢?这时候就要用到***ThreadLocal***了。bash
上面提到的同步变量,采起的是统一治理,而ThreadLocal采起的策略是分而治之。多线程
用官方的话来讲:ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(经过get或set方法访问)时能保证各个线程里的变量相对独立于其余线程内的变量。ThreadLocal实例一般来讲都是private static
类型的,用于关联线程和线程的上下文。并发
简单的说:源码分析
ThreadLocal提供了一个线程内部的变量副本,这个变量只在单个线程内部共享,在该线程内能够方便的访问ThreadLocal变量副本。post
多个线程间的TreadLocal变量副本互不影响。this
ThreadLocal只存活在线程的生命周期内,随着线程消亡而消亡(也能够手动调用remove方法移除ThreadLocal变量)。spa
这样一来就优雅的解决了单个线程中不一样方法传递参数的复杂度问题,由于是每一个线程内部共享变量,也不存在多线程不安全的问题了。
须要注意的是:
ThreadLocal不是同步机制,不解决多线程下的线程安全问题。
ThreadLocal为每个线程提供一个独立的变量副本,从而隔离了多个线程对数据的并发访问冲突。由于每个线程都拥有本身的变量副本,也就不存在多线程不安全的问题了。
咱们上面提到的传递用户信息的问题能够经过ThreadLocal实现:
public final class UserUtil {
private static ThreadLocal<User> userThreadLocal = new ThreadLocal();
public static User getUser() {
return userThreadLocal.get();
}
public static User setUser(User user) {
return userThreadLocal.set(user);
}
public static void removeUser() {
userThreadLocal.remove();
}
}
复制代码
//设置User
controller(){
UserUtil.setUser(user);
}
//获取User
serviceA(){
UserUtil.getUser();
}
serviceB(){
UserUtil.getUser();
}
dao(){
UserUtil.getUser();
}
复制代码
ThreadLocal为每个线程建立了一个独立的副本变量,那它到底怎么实现的呢?
从ThreadLocal源码中,能够得知以下信息:
经过调用***get***方法获取ThreadLocal对应的变量,***get***方法源码以下:
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//根据线程获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
//获取Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
T result = (T)e.value;
return result;
}
}
//设置并返回初始值
return setInitialValue();
}
复制代码
此方法先是获取了当前线程,再经过当前线程获取了一个***ThreadLocalMap*** ,若是ThreadLocalMap 为空则设置一个初始值并返回。若是获取的map不为空,则根据当前的***ThreadLocal***获取对应的value并返回。
根据这个方法咱们能够获取如下几点信息:
ThreadLocal对应的值都是存在***ThreadLocalMap***中。
ThreadLocalMap是根据***当前线程***实例实例获取的。
若是***ThreadLocalMap***为NULL或者ThreadLocal没有对应值的状况下,返回初始值(setInitialValue方法)。
既然知道了ThreadLocal的值是存在***ThreadLocalMap***中,咱们继续看一下ThreadLocalMap map = getMap(t);
这一段代码的具体实现。
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
复制代码
这个方法直接返回了,线程中的一个***threadLocals***属性,进一步跟踪能够发如今***Thread***类中定义了此属性。
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
...
}
复制代码
经过源码咱们已经很是清晰的知道,Thread***中保存了***ThreadLocalMap,而***ThreadLocalMap***中保存了***ThreadLocal***对应的键值对,示意图以下:
上一小节咱们已经分析了***get***方法的源码,接下来咱们分析一下其余的几段主要源码。
set方法:
public void set(T value) {
//获取当前线程
Thread t = Thread.currentThread();
//获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
复制代码
set***方法的做用是将当前***ThreadLocal实例***做为key,且将对应的value造成键值对保存进***ThreadLocalMap***中。若是***ThreadLocalMap***还未被建立则建立一个新的***ThreadLocalMap。
建立***ThreadLocalMap***,createMap(t, value)
方法具体实现以下。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
复制代码
***createMap***方法直接***new***了一个***ThreadLocalMap***对象,传入的参数时当前的***ThreadLocal***实例以及一个须要保存的变量值,跟踪进入***ThreadLocalMap***构造方法。
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//建立Entry数组
table = new Entry[INITIAL_CAPACITY];
//计算元素位置
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
//建立Entry
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
复制代码
***ThreadLocalMap***构造方法使用初始长度***INITIAL_CAPACITY***建立了一个***Entry***数组,并计算了初始元素的下标。
保存***ThreadLocal键值对***到数组对应的位置以后,设置size为1,而且初始化扩容下限。
由此代码可知,全部的***ThrealLocal键值对***最终保存的位置是一个***Entry数组***,Entry类定义在***ThreadLocalMap***类中。
public class ThreadLocal<T> {
...
static class ThreadLocalMap {
...
//弱引用
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...
}
...
}
复制代码
因为Entry继承了***WeakReference***,ThreadLocal被定义为弱引用对象,因此只要没有强引用指向ThreadLocal,那么触发GC时无论内存是否足够,都会把ThreadLocal回收掉。
可是这个时候有个问题,若是ThreadLocal被回收掉,而对应***Entry***中的value没有被回收,那这个value将永远没法访问到了。当愈来愈多的ThreadLocal被回收,对应的产生愈来愈多value没法被回收,最终会形成内存泄漏。
有人会说,那设计成强引用不就好了吗?其实设计成强引用并无效果,若是没有手动将引用设置成null,反而形成了key(ThreadLocal)和value都不被回收,最终也会形成内存泄漏。
设计成强引用还会形成value被手动设置成null,而key(ThreadLocal)一直不被回收的状况,一样会形成内存泄漏。
这就是为何ThreadLocal被设计成弱引用对象,当value被手动回收,同时会把ThreadLocal也进行回收,这无疑多了一份保险。
上面的分析也是经常有人提起使用ThreadLocal以后,须要手动的调用***set()、get()、remove()***不然会发生内存泄漏。
咱们能够看一下***remove()***是怎么处理这个问题的。
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
//清除key
e.clear();
//进一步清除
expungeStaleEntry(i);
return;
}
}
}
复制代码
***remove()***循环了table,而且调用了***e.clear()***用来清除key。
而接下来的***expungeStaleEntry***方法作了几件事情:
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;
//设置value和tab[staleSlot]为null
tab[staleSlot].value = null;
tab[staleSlot] = null;
size--;
Entry e;
int i;
//循环全部元素,并清除key==null的元素
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
//清除key==null的元素
if (k == null) {
e.value = null;
tab[i] = null;
size--;
} else {
//从新排列元素位置
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
tab[i] = null;
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
复制代码
一样的在ThreadLocal的***set()、get()***方法中最终也会调用***expungeStaleEntry***方法来清除key为null的值,此处就再也不赘述了。
ThreadLocal是用来解决线程内的资源共享,而且作到***线程间资源隔离***,可是某些场景咱们须要在子线程中访问主线程的资源,这是否能实现呢?固然能够,这个时候须要用到另一个类:InheritabIeThreadLocal。
在Thread源码中咱们很容易注意到***threadLocals***属性下方有另一个属性***inheritableThreadLocals***。
public class Thread implements Runnable {
...
ThreadLocal.ThreadLocalMap threadLocals = null;
//继承map
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
...
}
复制代码
这两个属性的类型都是***ThreadLocal.ThreadLocalMap***,而***inheritableThreadLocals***是被***InheritableThreadLocal***类所引用。
咱们先看一下***InheritableThreadLocal***类的源码
public class InheritableThreadLocal<T> extends ThreadLocal<T> {
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,覆盖了***childValue、getMap、createMap***这三个方法。
getMap、createMap***由原来操做***t.threadLocals***变成了操做***t.inheritableThreadLocals,而***childValue***方法会在***ThreadLocalMap***类建立继承Map中用到。
public class ThreadLocal<T> {
...
//建立一个继承map
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}
...
//根据parentMap建立一个新的ThreadLocalMap,其中的元素key和value值相同
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];
//循环建立新的Entry
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
//过滤key等于null的值
if (key != null) {
//childValue返回的值便是e.value
Object value = key.childValue(e.value);
//key,value值保持和父线程一致
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}
...
}
复制代码
createInheritedMap***是根据传入的***parentMap***复制了一个新的***ThreadLocalMap,过滤掉key等于null的值,其余的元素key和value值和父线程保持一致。
那线程是在何时建立继承map呢?当线程初始化时,会调用***Thread***类的***init***方法,而当咱们指定***inheritThreadLocals***为***true***而且父类线程***inheritableThreadLocals***不等于null的时候的时候,线程则会建立继承map。
public class Thread implements Runnable {
....
//绝大部分状况下调用本初始化方法
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
//inheritThreadLocals 默认为 true
init(g, target, name, stackSize, null, true);
}
//线程初始化
private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
...
//父类线程为建立本线程的运行线程
Thread parent = currentThread();
...
//建立继承map
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}
...
}
复制代码
这里有三点须要你们注意一下:
绝大部分状况下***inheritThreadLocals***默认为true。
父类线程为建立本线程的运行线程Thread parent = currentThread()
,也就是当前运行线程建立了一个新的线程,这个时候仍是运行线程在执行而新线程尚未初始化完毕,因此parent = currentThread()
。
子线程的***inheritableThreadLocals***,是根据父线程的***inheritableThreadLocals***复制得来的,至关于把父线程***inheritableThreadLocals***值传递下去,这样就实现了子线程获取到父线程的值。
下面经过一个例子比较一下***ThreadLocal***和***InheritableThreadLocal***的实际使用。
package com.gavin.test;
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
//设置主线程的值
ThreadLocalTest.threadLocal.set("threadLocal-main");
//启用线程1
new Thread(() -> {
//设置线程1的值
ThreadLocalTest.threadLocal.set("threadLocal-1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1==" + ThreadLocalTest.threadLocal.get());
}).start();
//启用线程2
new Thread(() -> {
//设置线程2的值
ThreadLocalTest.threadLocal.set("threadLocal-2");
System.out.println("线程2==" + ThreadLocalTest.threadLocal.get());
}).start();
System.out.println("主线程==" + ThreadLocalTest.threadLocal.get());
}
}
复制代码
本段代码定义了一个***threadLocal***属性,而且在三个线程中都对这个属性进行了设值,输出结果以下:
主线程==threadLocal-main
线程2==threadLocal-2
线程1==threadLocal-1
复制代码
由此能够得出三个线程的threadLocal属性互不干扰,咱们改变一下代码,看一会儿线程是否能取到主线程的值。
package com.gavin.test;
public class ThreadLocalTest {
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
ThreadLocalTest.threadLocal.set("threadLocal-main");
new Thread(() -> {
//直接打印值
System.out.println("线程1==" + ThreadLocalTest.threadLocal.get());
}).start();
new Thread(() -> {
//直接打印值
System.out.println("线程2==" + ThreadLocalTest.threadLocal.get());
}).start();
System.out.println("主线程==" + ThreadLocalTest.threadLocal.get());
}
}
复制代码
运行结果以下:
线程1==null
主线程==threadLocal-main
线程2==null
复制代码
由结果能够得出子线程并不能获取主线程的threadLocal值,再次证实线程间的threadLocal相互隔离。
为了实现子线程访问主线程的值,咱们尝试使用***inheritableThreadLocal***来实现。
package com.gavin.test;
public class InheritableThreadLocalTest {
private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-main");
new Thread(() -> {
System.out.println("线程1==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
}).start();
new Thread(() -> {
System.out.println("线程2==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
}).start();
System.out.println("主线程==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
}
}
复制代码
运行结果以下:
线程1==inheritableThreadLocal-main
主线程==inheritableThreadLocal-main
线程2==inheritableThreadLocal-main
复制代码
由结果得出子线程获得了主线程的值,假如子线程的值修改了,会影响主线程或者其余子线程吗?
根据上面的源码分析,主线程会把本身的***inheritableThreadLocal***传递给子线程,子线程从新new Entry对象用来保存key和value,因此子线程修改不会影响主线程的值,也不会影响其余子线程,只会向本身的子线程传递,咱们来验证一下吧。
package com.gavin.test;
public class InheritableThreadLocalTest {
private static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
public static void main(String[] args) {
InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-main");
new Thread(() -> {
InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
new Thread(() -> {
System.out.println("线程1的子线程==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
}).start();
}).start();
new Thread(() -> {
InheritableThreadLocalTest.inheritableThreadLocal.set("inheritableThreadLocal-2");
System.out.println("线程2==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
}).start();
System.out.println("主线程==" + InheritableThreadLocalTest.inheritableThreadLocal.get());
}
}
复制代码
运行结果以下
主线程==inheritableThreadLocal-main
线程2==inheritableThreadLocal-2
线程1==inheritableThreadLocal-1
线程1的子线程==inheritableThreadLocal-1
复制代码
正如咱们猜测的那样:
做者:GavinKing
原创不易,转载请取得做者赞成,并带上版权信息,谢谢