ThreadLocal终极源码剖析
本文较深入的分析了ThreadLocal和InheritableThreadLocal,从4个方向去分析:源码注释、源码剖析、功能测试、应用场景。
一、ThreadLocal
我们使用ThreadLocal解决线程局部变量统一定义问题,多线程数据不能共享。(InheritableThreadLocal特例除外)不能解决并发问题。解决了:基于类级别的变量定义,每一个线程单独维护自己线程内的变量值(存、取、删的功能)
根据源码,画出原理图如下:
注意点:
1.ThreadLocal类封装了getMap()、Set()、Get()、Remove()4个核心方法。
2.通过getMap()获取每个子线程Thread持有自己的ThreadLocalMap实例, 因此它们是不存在并发竞争的。可以理解为每个线程有自己的变量副本。
3.ThreadLocalMap中Entry[]数组存储数据,初始化长度16,后续每次都是2倍扩容。主线程中定义了几个变量,Entry[]才有几个key。
4.Entry
的key是对ThreadLocal的弱引用,当抛弃掉ThreadLocal对象时,垃圾收集器会忽略这个key的引用而清理掉ThreadLocal对象, 防止了内存泄漏。
1.1源码注释
理解原理最好的方法是看源码注释:
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID). For example, the class below generates unique identifiers local to each thread. A thread's id is assigned the first time it invokes ThreadId.get() and remains unchanged on subsequent calls.
这个类提供线程局部变量。这些变量与正常的变量不同,每个线程访问一个(通过它的get或set方法)都有它自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,希望将状态与线程关联(例如,用户ID或事务ID)。
注释中的示例代码:
下图ThreadId类会在每个线程中生成唯一标识符。线程的id在第一次调用threadid.get()时被分配,在随后的调用中保持不变。
ThreadId类利用AtomicInteger原子方法getAndIncrement,为每个线程创建一个threadId变量,例如第一个线程是1,第二个线程是2...,并提供一个类静态get方法用以获取当前线程ID。:
import java.util.concurrent.atomic.AtomicInteger; public class ThreadId {
// Atomic integer containing the next thread ID to be assigned
private static final AtomicInteger nextId = new AtomicInteger(0); // Thread local variable containing each thread's ID
private static final ThreadLocal<Integer> threadId =
new ThreadLocal<Integer>() {
@Override protected Integer initialValue() {
return nextId.getAndIncrement();
}
}; // Returns the current thread's unique ID, assigning it if necessary
public static int get() {
return threadId.get();
}
}
如上图,有一个注意点是:用户可以自定义initialValue()初始化方法,来初始化threadLocal的值。
1.2 源码剖析
我们来追踪一下ThreadLocal源码:
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();
} 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;
} public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
} public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
} ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
看源码我们知道不管是set、get、remove操作的都是ThreadLocalMap,key=当前线程,value=线程局部变量缓存值。
上图getMap最终调用的Thread的成员变量 ThreadLocal.ThreadLocalMap threadLocals,如下图:
ThreadLocalMap是ThreadLocal的一个内部类,源码注释:
ThreadLocalMap是一个定制的哈希映射,仅适用于维护线程本地值。ThreadLocalMap类是包私有的,允许在Thread类中声明字段。为了帮助处理非常大且长时间的使用,哈希表entry使用了对键的弱引用。有助于GC回收。
散列算法-魔数0x61c88647
ThreadLocal中定义了一个AtomicInteger,一个魔数0x61c88647,利用一定算法实现了元素的完美散列。
源码中元素散列算法如下:
1.求hashCode = i*HASH_INCREMENT+HASH_INCREMENT每次新增一个元素(threadLocal)进Entry[],自增0x61c88647
2.元素散列位置(数组下标)= hashCode & (length-1),
下面校验算法的散列性:
/**
*
* @ClassName:MagicHashCode
* @Description:ThreadLocalMap使用“开放寻址法”中最简单的“线性探测法”解决散列冲突问题
* @author diandian.zhang
* @date 2017年12月6日上午10:53:28
*/
public class MagicHashCode {
//ThreadLocal中定义的hash魔数
private static final int HASH_INCREMENT = 0x61c88647; public static void main(String[] args) {
hashCode(16);//初始化16
hashCode(32);//后续2倍扩容
hashCode(64);
} /**
*
* @Description 寻找散列下标(对应数组小标)
* @param length table长度
* @author diandian.zhang
* @date 2017年12月6日上午10:36:53
* @since JDK1.8
*/
private static void hashCode(Integer length){
int hashCode = 0;
for(int i=0;i<length;i++){
hashCode = i*HASH_INCREMENT+HASH_INCREMENT;//每次递增HASH_INCREMENT
System.out.print(hashCode & (length-1));//求散列下标,算法公式
System.out.print(" ");
}
System.out.println();
}
}
运行结果:
7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0 --》Entry[]初始化容量为16时,元素完美散列
7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26 1 8 15 22 29 4 11 18 25 0--》Entry[]容量扩容2倍=32时,元素完美散列
7 14 21 28 35 42 49 56 63 6 13 20 27 34 41 48 55 62 5 12 19 26 33 40 47 54 61 4 11 18 25 32 39 46 53 60 3 10 17 24 31 38 45 52 59 2 9 16 23 30 37 44 51 58 1 8 15 22 29 36 43 50 57 0 --》Entry[]容量扩容2倍=64时,元素完美散列
根据运行结果,代表此算法在长度为2的N次方的数组上,确实可以完美散列。
那么原理是什么?
long l1 = (long) ((1L << 31) * (Math.sqrt(5) - 1));//(根号5-1)*2的31次方=(根号5-1)/2 *2的32次方=黄金分割数*2的32次方
System.out.println("as 32 bit unsigned: " + l1);//32位无符号整数
int i1 = (int) l1;
System.out.println("as 32 bit signed: " + i1);//32位有符号整数
System.out.println("MAGIC = " + 0x61c88647);
运行结果:
as 32 bit unsigned: 2654435769
as 32 bit signed: -1640531527
MAGIC = 1640531527
这里不再拓展,跟斐波那契数列(和黄金分割数)有关:
1.0x61c88647对应十进制=1640531527。
2.(根号5-1)*2的31次方,转换成long类型就是2654435769,转换成int类型就是-1640531527。
set操作
ThreadLocal的set最终调用了ThreadLocalMap的set方法,如下图
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);// 根据
哈希码和数组长度求元素放置的位置,即数组下标
//从i开始往后一直遍历到数组最后一个Entry
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
//如果key相等,覆盖value
if (k == key) {
e.value = value;
return;
}
//如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
} tab[i] = new Entry(key, value);
int sz = ++size;
//如果超过阀值,就需要再哈希了
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
再哈希:
private void rehash() {
expungeStaleEntries();// 清理一次陈旧数据 // 清理完陈旧数据,如果>= 3/4阀值,就执行扩容,避免迟滞
if (size >= threshold - threshold / 4)
resize();
} /**
* 把table扩容2倍,并把老数据重新哈希散列进新table
*/
private void resize() {
Entry[] oldTab = table;
int oldLen = oldTab.length;
int newLen = oldLen * 2;
Entry[] newTab = new Entry[newLen];
int count = 0;
// 遍历Entry[]数组
for (int j = 0; j < oldLen; ++j) {
Entry e = oldTab[j];
if (e != null) {
ThreadLocal<?> k = e.get();
if (k == null) {// 如果key=null
e.value = null; // 把value也置null,有助于GC回收对象
} else {// 如果key!=null
int h = k.threadLocalHashCode & (newLen - 1);// 计算hash值
while (newTab[h] != null)// 如果这个位置已使用
h = nextIndex(h, newLen);// 线性往后查询,直到找到一个没有使用的位置,h递增
newTab[h] = e;//在第一个空节点上塞入Entry e
count++;// 计数++
}
}
} setThreshold(newLen);// 设置新的阈值(实际set方法用了2/3的newLen作为阈值)
size = count;// 设置ThreadLocalMap的元素个数
table = newTab;// 把新table赋值给ThreadLocalMap的Entry[] table
} /**
* 删除陈旧的数据
*/
private void expungeStaleEntries() {
Entry[] tab = table;
int len = tab.length;
for (int j = 0; j < len; j++) {
Entry e = tab[j];
if (e != null && e.get() == null)//entry不为空且entry的key为null
expungeStaleEntry(j);//删除指定数组下标的陈旧entry
}
}
//删除陈旧entry的核心方法
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length; tab[staleSlot].value = null;//删除value
tab[staleSlot] = null;//删除entry
size--;//map的size自减 // 遍历指定删除节点,所有后续节点
Entry e;
int i;
for (i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {//key为null,执行删除操作
e.value = null;
tab[i] = null;
size--;
} else {//key不为null,重新计算下标
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {//如果不在同一个位置
tab[i] = null;//把老位置的entry置null(删除) // 从h开始往后遍历,一直到找到空为止,插入
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i;
}
总结set步骤:
1)根据
哈希码和数组长度求元素放置的位置,即数组下标
2)从第一步得出的下标开始往后遍历,如果key相等,覆盖value,如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据
3)如果超过阀值,就需要再哈希:
- 清理一遍陈旧数据
- >= 3/4阀值,就执行扩容,把table扩容2倍==》注意这里3/4阀值就执行扩容,避免迟滞
- 把老数据重新哈希散列进新table
get操作
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);//从当前线程中获取ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);//查询当前ThreadLocal变量实例对应的Entry
if (e != null) {//如果不为null,获取value,返回
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}//如果map为null,即还没有初始化,走初始化方法
return setInitialValue();
} private T setInitialValue() {
T value = initialValue();//该方法默认返回null,用户可自定义
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)//如果map不为null,把初始化value设置进去
map.set(this, value);
else//如果map为null,则new一个map,并把初始化value设置进去
createMap(t, value);
return value;
} void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
} ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];//初始化容量16
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);//设置阈值
}
//阈值设置为容量的*2/3,即负载因子为2/3,超过就进行再哈希
private void setThreshold(int len) {
threshold = len * 2 / 3;
}
总结get步骤:
1)从当前线程中获取ThreadLocalMap,查询当前ThreadLocal变量实例对应的Entry,如果不为null,获取value,返回
2)如果map为null,即还没有初始化,走初始化方法
remove操作
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);//调用ThreadLocalMap删除变量
} 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) {
e.clear();//调用Entry的clear方法
expungeStaleEntry(i);//清除陈旧数据
return;
}
}
}
看一下Entry的clear方法,Entry ==extends==》 WeakReference<ThreadLocal<?>>==extends==》 Reference<T>,clear方法是抽象类Reference定义的方法。
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
追一下clear方法如下:把弱引用的对象置null。有利于GC回收内存。关于引用,预留飞机票
public void clear() {
this.referent = null;
}
1.3 功能测试
开启2个线程,每个个线程都使用类级别的threadLocal,往里面递增数字,i=0,时,set(0),i=1,2,3时 值+1,
1 /**
2 *
3 * @ClassName:MyThreadLocal
4 * @Description:ThreadLocal线程本地变量
5 * @author diandian.zhang
6 * @date 2017年12月4日上午9:40:52
7 */
8 public class MyThreadLocal{
9 //线程本地共享变量
10 private static final ThreadLocal<Object> threadLocal = new ThreadLocal<Object>(){
11 /**
12 * ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
13 */
14 @Override
15 protected Object initialValue()
16 {
17 System.out.println("[线程"+Thread.currentThread().getName()+"]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!");
18 return null;
19 }
20 };
21
22 public static void main(String[] args){
23 //1.开启任务1线程
24 new Thread(new MyIntegerTask("IntegerTask1")).start();
25 //2.中间休息3秒,用以测试数据差异
26 try {
27 Thread.sleep(3000);
28 } catch (InterruptedException e) {
29 e.printStackTrace();
30 }
31 //3.开启任务2线程
32 new Thread(new MyIntegerTask("IntegerTask2")).start();
33 }
34
35 /**
36 *
37 * @ClassName:MyIntegerTask
38 * @Description:整形递增线程
39 * @author diandian.zhang
40 * @date 2017年12月4日上午10:00:41
41 */
42 public static class MyIntegerTask implements Runnable{
43 private String name;
44
45 MyIntegerTask(String name)
46 {
47 this.name = name;
48 }
49
50 @Override
51 public void run()
52 {
53 for(int i = 0; i < 5; i++)
54 {
55 // ThreadLocal.get方法获取线程变量
56 if(null == MyThreadLocal.threadLocal.get())
57 {
58 // ThreadLocal.set方法设置线程变量
59 MyThreadLocal.threadLocal.set(0);
60 System.out.println("i="+i+"[线程" + name + "]当前线程不存在缓存,set 0");
61 }
62 else
63 {
64 int num = (Integer)MyThreadLocal.threadLocal.get();
65 MyThreadLocal.threadLocal.set(num + 1);
66 System.out.println("i="+i+"[线程" + name + "]往threadLocal中set: " + MyThreadLocal.threadLocal.get());
67 //当i=3即循环4次时,移除当前线程key
68 if(i == 3)
69 {
70 System.out.println("i="+i+"[线程" + name + "],threadLocal移除当前线程" );
71 MyThreadLocal.threadLocal.remove();
72 }
73 }
74 try
75 {
76 Thread.sleep(1000);
77 }
78 catch (InterruptedException e)
79 {
80 e.printStackTrace();
81 }
82 }
83 }
84 }
85 }
运行结果如下:
[线程Thread-0]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=0[线程IntegerTask1]当前线程不存在缓存,set 0
i=1[线程IntegerTask1]往threadLocal中set: 1
i=2[线程IntegerTask1]往threadLocal中set: 2
[线程Thread-1]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=0[线程IntegerTask2]当前线程不存在缓存,set 0
i=3[线程IntegerTask1]往threadLocal中set: 3
i=3[线程IntegerTask1],threadLocal移除当前线程
i=1[线程IntegerTask2]往threadLocal中set: 1
[线程Thread-0]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=4[线程IntegerTask1]当前线程不存在缓存,set 0
i=2[线程IntegerTask2]往threadLocal中set: 2
i=3[线程IntegerTask2]往threadLocal中set: 3
i=3[线程IntegerTask2],threadLocal移除当前线程
[线程Thread-1]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=4[线程IntegerTask2]当前线程不存在缓存,set 0
结果验证:
1.2个线程,2个threadLocal变量互不影响。
2.调用get方法时,对应ThreadLocalMap为null会调用initialValue()方法,初始化threadLocal的值。
1.4 应用场景
ThreadLocal的实际应用场景:
1)数据结构:用Map<String, Object>来避免创建多个ThreadLocal变量的麻烦。只需根据map的key就可以获取想要的value
private static final ThreadLocal<Map<String, Object>> loginContext = new ThreadLocal<>();
2)业务:线程级别,维护session,维护用户登录信息userID(登陆时插入,多个地方获取)等,尤其适合使用在WEB项目中(Tomcat容器,工作线程隔离)
二、变量可继承的ThreadLocal==》InheritableThreadLocal
2.1 源码注释:
这个类扩展ThreadLocal,以提供从父线程到子线程的值的继承:当创建子线程时,子线程会接收父元素所具有值的所有可继承线程局部变量的初始值。正常情况下,子线程的变量值与父线程的相同;然而,子线程可复写childValue方法来自定义获取父类变量。
当变量(例如,用户ID、事务ID)中维护的每个线程属性必须自动传输到创建的任何子线程时,使用InheritableThreadLocal优于ThreadLocal。
2.2 源码剖析
1.子线程启动时,调用init方法,如果父线程有InheritableThreadLocal变量,则在子线程也生成一份
下图是Thread类在init时执行的逻辑:
调用createInheritedMap方法,并调用childValue方法复制一份变量给子线程
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
} private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len]; 11 for (int j = 0; j < len; j++) {
12 Entry e = parentTable[j];
13 if (e != null) {
14 @SuppressWarnings("unchecked")
15 ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
16 if (key != null) {
17 Object value = key.childValue(e.value);
18 Entry c = new Entry(key, value);
19 int h = key.threadLocalHashCode & (len - 1);
20 while (table[h] != null)
21 h = nextIndex(h, len);
22 table[h] = c;
23 size++;
24 }
25 }
26 }
27 }
2.支持用户自定义childValue函数,用以子类获取父类变量值的转换:父类变量----childValue转换函数-----》子类变量
InheritableThreadLocal默认childValue函数是直接返回:
protected T childValue(T parentValue) {
return parentValue;
}
用户可在创建InheritableThreadLocal变量时,覆盖childValue函数,见3.3测试
2.3 功能测试
package threadLocal; /**
*
* @ClassName:MyInheritableThreadLocal
* @Description:可继承线程本地变量
* @author denny.zhang
* @date 2017年12月7日下午5:24:40
*/
public class MyInheritableThreadLocal{
//线程本地共享变量
private static final InheritableThreadLocal<Object> threadLocal = new InheritableThreadLocal<Object>(){
/**
* ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值
*/
@Override
protected Object initialValue()
{
System.out.println("[线程"+Thread.currentThread().getName()+"]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!");
return null;
} @Override
protected Object childValue(Object parentValue) {
return (Integer)parentValue*2;
} }; public static void main(String[] args){
//主线程设置1
threadLocal.set(1);
//1.开启任务1线程
new Thread(new MyIntegerTask("IntegerTask1")).start();
//2.中间休息3秒,用以测试数据差异
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//开启任务2线程
new Thread(new MyIntegerTask("IntegerTask2")).start();
} /**
*
* @ClassName:MyIntegerTask
* @Description:整形递增线程
* @author diandian.zhang
* @date 2017年12月4日上午10:00:41
*/
public static class MyIntegerTask implements Runnable{
private String name; MyIntegerTask(String name)
{
this.name = name;
} @Override
public void run()
{
for(int i = 0; i < 5; i++)
{
// ThreadLocal.get方法获取线程变量
if(null == MyInheritableThreadLocal.threadLocal.get())
{
// ThreadLocal.set方法设置线程变量
MyInheritableThreadLocal.threadLocal.set(0);
System.out.println("i="+i+"[线程" + name + "]当前线程不存在缓存,set 0");
}
else
{
int num = (Integer)MyInheritableThreadLocal.threadLocal.get();
System.out.println("i="+i+"[线程" + name + "]get=" + num);
MyInheritableThreadLocal.threadLocal.set(num + 1);
System.out.println("i="+i+"[线程" + name + "]往threadLocal中set: " + MyInheritableThreadLocal.threadLocal.get());
//当i=3即循环4次时,移除当前线程key
if(i == 3)
{
System.out.println("i="+i+"[线程" + name + "],remove" );
MyInheritableThreadLocal.threadLocal.remove();
}
}
try
{
Thread.sleep(1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
}
运行结果:
主线程变量值=1-----》主线程中变量值1
i=0[线程IntegerTask1]get=2-----》子线程1中变量值=2*1=2,验证通过!
i=0[线程IntegerTask1]往threadLocal中set: 3
i=1[线程IntegerTask1]get=3
i=1[线程IntegerTask1]往threadLocal中set: 4
i=2[线程IntegerTask1]get=4
i=2[线程IntegerTask1]往threadLocal中set: 5
i=0[线程IntegerTask2]get=2-----》主线程2中变量值=2*1=2,验证通过!
i=0[线程IntegerTask2]往threadLocal中set: 3
i=3[线程IntegerTask1]get=5
i=3[线程IntegerTask1]往threadLocal中set: 6
i=3[线程IntegerTask1],remove
i=1[线程IntegerTask2]get=3
i=1[线程IntegerTask2]往threadLocal中set: 4
[线程Thread-0]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=4[线程IntegerTask1]当前线程不存在缓存,set 0
i=2[线程IntegerTask2]get=4
i=2[线程IntegerTask2]往threadLocal中set: 5
i=3[线程IntegerTask2]get=5
i=3[线程IntegerTask2]往threadLocal中set: 6
i=3[线程IntegerTask2],remove
[线程Thread-1]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!
i=4[线程IntegerTask2]当前线程不存在缓存,set 0
如上图,分析结果我们可知,
1.子线程根据childValue函数获取到了父线程的变量值。
2.多线程InheritableThreadLocal变量各自维护,无竞争关系。
2.4 应用场景
子线程变量数据依赖父线程变量,且自定义赋值函数。
例如:
开启多线程执行任务时,总任务名称叫mainTask 子任务名称依次递增mainTask-subTask1、mainTask-subTask2、mainTask-subTaskN等等
三、总结
本文分析了ThreadLocal原理、set(散列算法原理和测试验证,再哈希扩容)、get、remove源码,实际中的应用场景以及功能测试验证。最后又分析了InheritableThreadLocal,使用该类子线程会继承父线程变量,并自定义赋值函数。
读完本文,相信大家对ThreadLocal一点也不担心了哈哈!
需要注意2点:
1.ThreadLocal不是用来解决线程安全问题的,多线程不共享,不存在竞争!目的是线程本地变量且只能单个线程内维护使用。
2.InheritableThreadLocal对比ThreadLocal唯一不同是子线程会继承父线程变量,并自定义赋值函数。
3.项目如果使用了线程池,那么小心线程回收后ThreadLocal、InheritableThreadLocal变量要remove,否则线程池回收后,变量还在内存中,后果不堪设想!(例如Tomcat容器的线程池,可以在拦截器中处理:extends HandlerInterceptorAdapter,然后复写afterCompletion方法,remove掉变量!!!)
=========参考=============
ThreadLocal终极源码剖析的更多相关文章
- ThreadLocal终极源码剖析-一篇足矣!
本文较深入的分析了ThreadLocal和InheritableThreadLocal,从4个方向去分析:源码注释.源码剖析.功能测试.应用场景. 一.ThreadLocal 我们使用ThreadLo ...
- Java多线程9:ThreadLocal源码剖析
ThreadLocal源码剖析 ThreadLocal其实比较简单,因为类里就三个public方法:set(T value).get().remove().先剖析源码清楚地知道ThreadLocal是 ...
- 并发编程之 ThreadLocal 源码剖析
前言 首先看看 JDK 文档的描述: 该类提供了线程局部 (thread-local) 变量.这些变量不同于它们的普通对应物,因为访问某个变量(通过其 get 或 set 方法)的每个线程都有自己的局 ...
- java多线程17:ThreadLocal源码剖析
ThreadLocal源码剖析 ThreadLocal其实比较简单,因为类里就三个public方法:set(T value).get().remove().先剖析源码清楚地知道ThreadLocal是 ...
- SpringMVC源码剖析(四)- DispatcherServlet请求转发的实现
SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段.在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过serv ...
- jdk源码剖析:Synchronized
开启正文之前,先说一下源码剖析这一系列,就以"死磕到底"的精神贯彻始终,最少追踪到JVM指令(再往下C语言实现了). =========正文分割线=========== Sync ...
- Guava 12:Guava EventBus源码剖析
一.架构速读 传统上,Java的进程内事件分发都是通过发布者和订阅者之间的显式注册实现的.设计EventBus就是为了取代这种显示注册方式,使组件间有了更好的解耦.EventBus不是通用型的发布-订 ...
- Flask核心机制--上下文源码剖析
一.前言 了解过flask的python开发者想必都知道flask中核心机制莫过于上下文管理,当然学习flask如果不了解其中的处理流程,可能在很多问题上不能得到解决,当然我在写本篇文章之前也看到了很 ...
- jdk源码剖析三:锁Synchronized
一.Synchronized作用 (1)确保线程互斥的访问同步代码 (2)保证共享变量的修改能够及时可见 (3)有效解决重排序问题.(Synchronized同步中的代码JVM不会轻易优化重排序) 二 ...
随机推荐
- UWP 磁贴设置
一:需求 一款好看好用的应用,对于UWP来说,动态的磁贴必不可少. 二:TileUpdateManager类 和TileUpdater类 如果需要更改或更新应用的磁贴,那么首先需要获得TileUpda ...
- Android的主线程和子线程
在一个Android 程序开始运行的时候,会单独启动一个Process.默认的情况下,所有这个程序中的Activity或者Service(Service和 Activity只是Android提供的Co ...
- Windows环境下多线程编程原理与应用读书笔记(8)————信号量及其应用
<一>线程间同步原因 线程间竞争共享资源: 线程间为完成某个任务而协作: 通过互斥量可以实现线程间由于竞争所需要的同步,通过事件可以实现线程间由于协作所需要的同步. 信号量很好地将互斥量和 ...
- hdu 2196 Computer(树形DP经典)
Computer Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Su ...
- hdu 1011 Starship Troopers(树形DP入门)
Starship Troopers Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Other ...
- caffe源码分析 vector<Blob<Dtype>*>& bottom
Blob:4个维度 n x c x h x w: bottom[0] .bottom[1]代表该层有几个输入. bottom[0]->count(): 输入中,元素的总维数(个数) bottom ...
- String类型
String字符串的length属性返回的是该字符串里面16位字符的数量,如果字符串包含double-byte的字符,那么返回的数量可能不对 字符串一旦创建就修改不了 var lang = “Ja ...
- PTA 循环单链表区间删除 (15 分)
本题要求实现带头结点的循环单链表的创建和单链表的区间删除.L是一个带头结点的循环单链表,函数ListCreate_CL用于创建一个循环单链表,函数ListDelete_CL用于删除取值大于min小于m ...
- 绕过校园网WEB认证_dns2tcp实现
相信很多高校学生都有用WEB认证方式接入校园网的经历 拿我所在的大学为例,我们大学的校园网由联通公司承建,当我连上寝室的无线路由器后,浏览器会自动弹出一个由卓智公司开发的认证界面,如下图: 如果买了联 ...
- Vim常用操作-Nginx配置文件批量加注释。
刚接触 Vim 会觉得它的学习曲线非常陡峭,要记住很多命令.所以这个系列的分享,不会教你怎么配置它,而是教你怎么快速的使用它. 本期我们要实现给 Nginx 配置文件批量注释的功能,先来看效果: 操作 ...