Java并发编程:ThreadLocal
来源:互联网 发布:时代光华管理课程知乎 编辑:程序博客网 时间:2024/05/15 22:41
ThreadLocal特性及使用场景:
1、方便同一个线程使用某一对象,避免不必要的参数传递;
2、线程间数据隔离(每个线程在自己线程里使用自己的局部变量,各线程间的ThreadLocal对象互不影响);
3、获取数据库连接、Session、关联ID(比如日志的uniqueID,方便串起多个日志);
ThreadLocal应注意:
1、ThreadLocal并未解决多线程访问共享对象的问题;
2、ThreadLocal并不是每个线程拷贝一个对象,而是直接new(新建)一个;
3、如果ThreadLocal.set()的对象是多线程共享的,那么还是涉及并发问题。
1、ThreadLocal<T>初始化
private final int threadLocalHashCode = nextHashCode();
private static final intHASH_INCREMENT=0x61c88647;
/**
* The next hash code to be given out. Updated atomically.Starts at zero.
*/
// 源码说nextHashCode初始值为0,但实际调试时显示初始值为1253254570,费解?
// 而且当初始化完毕后,nextHashCode的值又变为0,说明其初始值确实是0的。
ThreadLocal类变量有3个,其中2个是静态变量(包括一个常量),实际作为作为ThreadLocal实例的变量只有threadLocalHashCode这1个,而且已经初始化就不可变了。
创建ThreadLocal实例时有哪些操作呢:
ThreadLocal初始化时会调用nextHashCode()方法初始化threadLocalHashCode,且threadLocalHashCode初始化后不可变。threadLocalHashCode可用来标记不同的ThreadLocal实例。
2、内部类
2.1 ThreadLocalMap
ThreadLocalMap是定制的hashMap,仅用于维护当前线程的本地变量值。仅ThreadLocal类对其有操作权限,是Thread的私有属性。为避免占用空间较大或生命周期较长的数据常驻于内存引发一系列问题,hash table的key是弱引用WeakReferences。当空间不足时,会清理未被引用的entry。
ThreadLocalMap中的重点:
Note:
ThreadLocalMap的key是ThreadLocal,value是Object(即我们所谓的“线程本地数据”)。
2.2 SuppliedThreadLocal<T> extends ThreadLocal<T>
SuppliedThreadLocal是JDK8新增的内部类,只是扩展了ThreadLocal的初始化值的方法而已,允许使用JDK8新增的Lambda表达式赋值。需要注意的是,函数式接口Supplier不允许为null。
源码如下:
3、主要方法
3.1、T get()
返回当前线程的value。
-----
getMap的源码:
getMap(t)返回当前线程的成员变量ThreadLocalMap(Thread的成员变量有ThreadLocalMap,这一点可以查看Thread的源码,如下)很明确的说明了ThreadLocal属于线程,ThreadLocalMap由ThreadLocal持有,说到底,ThreadLocalMap 也是线程所持有。每个线程Thread都有自己的ThreadLocalMap。
/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
--------
setInitialValue源码:
createMap源码:
ThreadLocal之get流程:
1、获取当前线程t;
2、返回当前线程t的成员变量ThreadLocalMap(以下简写map);
3、map不为null,则获取以当前线程为key的ThreadLocalMap的Entry(以下简写e),如果e不为null,则直接返回该Entry的value;
4、如果map为null或者e为null,返回setInitialValue()的值。setInitialValue()调用重写的initialValue()返回新值(如果没有重写initialValue将返回默认值null),并将新值存入当前线程的ThreadLocalMap(如果当前线程没有ThreadLocalMap,会先创建一个)。
3.2、void set(T value)
为【当前线程】的【当前ThreadLocal】赋值(初始值or新值)。和setInitialValue相当相似,就不多分析了。
3.3、void remove()
获取当前线程的ThreadLocalMap,map不为空,则移除当前ThreadLocal作为key的键值对。
Note:
remove()移除当前线程的当前ThreadLocal数据(只是清空该key-value键值对),而且是立即移除,移除后,再调用get方法将重新调用initialValue方法初始化(除非在此期间调用了set方法赋值)。
3.4、static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier)
JDK8新增,支持Lambda表达式,和ThreadLocal重写的initialValue()效果一样。
可以看出,withInitial()方法的入参是函数式接口Supplier,返回值是JDK8新增的内部类SuppliedThreadLocal,正如2.2所说,区别仅在于支持Lambda表达式赋值而已。使用事例如下:
Note:
·withInitial(supplier)是有返回值ThreadLocal的,So实例化时需将其赋值给ThreadLocal实例。
4、图解ThreadLocal
每个线程可能有多个ThreadLocal,同一线程的各个ThreadLocal存放于同一个ThreadLocalMap中。
图解ThreadLocal(JDK8).vsdx原图下载地址:https://github.com/zxiaofan/JDK-Study/tree/master/src/java1/lang/threadLocal
5、ThreadLocal-ThreadLocalMap源码分析
5.1、Entry getEntry(ThreadLocal<?> key)
首先来看get方法,你会发现ThreadLocalMap的get方法和传统Map不同,其返回的不是key-value的value,而是整个entry,当时entry的key是ThreadLocal,value是存放的值,这点是一致的。
a、getEntry源码分析:
getEnrty方法只会处理key被直接命中的entry,没有直接命中的(key冲突的)数据将调用getEntryAfterMiss()方法返回对应enrty,按照源码解释,这样做是为了尽可能提升直接命中的性能。
ThreadLocalMap之getEntry的流程:
1、计算Entry数组的index((length - 1) & key.hash)。
索引计算和HashMap的异同:
①相似之处:计算方式相同,均为(length - 1) & key.hash;length均为底层结构的大小(是大小,不是实际size)。
②不同之处:HashMap(JDK8)底层数据结构是位桶+链表/红黑树,而ThreadLocalMap底层数据结构是Entry数组;HashMap的key.hash的计算方式是native、异或、无符号位移,(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16),ThreadLocalMap的key.hash从ThreadLocal实例化时便由nextHashCode()确定。
2、获取对应index的节点Entry;
3、如果返回节点entry 有值且其key未冲突(只有1个即entry返回的key等于传入的key),则直接返回该entry;
4、返回entry为空或键冲突,则调用getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)方法返回entry。
b、getEntryAfterMiss源码分析:
getEntryAfterMiss处理那些getEntry时没有被命中的key(value为空的直接返回null,so更确切的说是命中且有冲突的key)。入参是当前ThreadLocal,key在数组的索引index,以及index对应的键值对。
ThreadLocalMap之getEntryAfterMiss的流程:
仅分析Entry不为空的情况,
1、获取entry的key;
2、如果key一致(内存地址=判断),则返回该entry;
3、如果key为null,则调用expungeStaleEntry方法擦除该entry;
4、其他情况则通过nextIndex方法获取下一个索引位置index;
5、获取新index处的entry,再死循环2/3/4,直到定位到该key返回entry或者返回null。
c、expungeStaleEntry源码分析:
只要key为null均会被擦除,使得对应value没有被引用,方便回收。
5.2、set(ThreadLocal<?> key, Object value)
rehash():
size:table的实际entry数量;扩容阈值threshold:table.lenrth(默认16)大小的2/3;
首先调用expungeStaleEntries删除所有过期数据,如果清理数据后size>=threshold的3/4,则2倍扩容。
ps:阈yù值又叫临界值,是指一个效应能够产生的最低值或最高值。阀fá 控制、开关、把持。
ThreadLocalMap和HashMap在hash冲突时的解决方案对比:
HashMap:若冲突则将新数据按链表或红黑树逻辑插入。
详见《HashMap源码分析(jdk1.8)》
put(K key, V value)的逻辑:
1、判断键值对数组tab[]是否为空或为null,是则resize();
2、根据键值key的hashCode()计算hash值得到当前Node的索引i(bucketIndex),如果tab[i]==null【没碰撞】,直接新建节点添加,否则【碰撞】转入3
3、判断当前数组中处理hash冲突的方式为红黑树还是链表(check第一个节点类型即可),分别处理。【①是红黑树则按红黑树逻辑插入;②是链表,则遍历链表,看是否有key相同的节点;③有则更新value值,没有则新建节点,此时若链表数量大于阀值8【9个】,则调用treeifyBin方法(此方法先判断table是否为null或tab.length小于64,是则执行resize操作,否则才将链表改为红黑树)。】
4、如果size+1> threshold则resize。
ThreadLocalMap:
1、若指定位置index已有数据entry,逐个遍历entry:
1.1、若index处key相同,则更新value;
1.2、若index处key为null,则调用replaceStaleEntry清理过期数据并插入新数据(从index处挨个遍历,直到找到相同key更新value结束,或者一直未找到,则在index处放入new Entry)。replaceStaleEntry遍历时会将entry逐个后移,也就是说set进去的最新entry一定会放在index处,方便get时直接命中。
2、index处无数据,则放入新entry;随后清理过期数据并判断是否2倍扩容(size>=threshold的3/4)。
参考资料:
http://www.cnblogs.com/dolphin0520/p/3920407.html
http://blog.csdn.net/u010887744/article/details/54730556
有任何问题,欢迎指正探讨。
参考
ThreadLocal特性及使用场景:
1、方便同一个线程使用某一对象,避免不必要的参数传递;
2、线程间数据隔离(每个线程在自己线程里使用自己的局部变量,各线程间的ThreadLocal对象互不影响);
3、获取数据库连接、Session、关联ID(比如日志的uniqueID,方便串起多个日志);
ThreadLocal应注意:
1、ThreadLocal并未解决多线程访问共享对象的问题;
2、ThreadLocal并不是每个线程拷贝一个对象,而是直接new(新建)一个;
3、如果ThreadLocal.set()的对象是多线程共享的,那么还是涉及并发问题。
1、ThreadLocal<T>初始化
private final int threadLocalHashCode = nextHashCode();
private static final intHASH_INCREMENT=0x61c88647;
/**
* The next hash code to be given out. Updated atomically.Starts at zero.
*/
// 源码说nextHashCode初始值为0,但实际调试时显示初始值为1253254570,费解?
// 而且当初始化完毕后,nextHashCode的值又变为0,说明其初始值确实是0的。
ThreadLocal类变量有3个,其中2个是静态变量(包括一个常量),实际作为作为ThreadLocal实例的变量只有threadLocalHashCode这1个,而且已经初始化就不可变了。
创建ThreadLocal实例时有哪些操作呢:
ThreadLocal初始化时会调用nextHashCode()方法初始化threadLocalHashCode,且threadLocalHashCode初始化后不可变。threadLocalHashCode可用来标记不同的ThreadLocal实例。
2、内部类
2.1 ThreadLocalMap
ThreadLocalMap是定制的hashMap,仅用于维护当前线程的本地变量值。仅ThreadLocal类对其有操作权限,是Thread的私有属性。为避免占用空间较大或生命周期较长的数据常驻于内存引发一系列问题,hash table的key是弱引用WeakReferences。当空间不足时,会清理未被引用的entry。
ThreadLocalMap中的重点:
Note:
ThreadLocalMap的key是ThreadLocal,value是Object(即我们所谓的“线程本地数据”)。
2.2 SuppliedThreadLocal<T> extends ThreadLocal<T>
SuppliedThreadLocal是JDK8新增的内部类,只是扩展了ThreadLocal的初始化值的方法而已,允许使用JDK8新增的Lambda表达式赋值。需要注意的是,函数式接口Supplier不允许为null。
源码如下:
3、主要方法
3.1、T get()
返回当前线程的value。
-----
getMap的源码:
getMap(t)返回当前线程的成员变量ThreadLocalMap(Thread的成员变量有ThreadLocalMap,这一点可以查看Thread的源码,如下)很明确的说明了ThreadLocal属于线程,ThreadLocalMap由ThreadLocal持有,说到底,ThreadLocalMap 也是线程所持有。每个线程Thread都有自己的ThreadLocalMap。
/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
--------
setInitialValue源码:
createMap源码:
ThreadLocal之get流程:
1、获取当前线程t;
2、返回当前线程t的成员变量ThreadLocalMap(以下简写map);
3、map不为null,则获取以当前线程为key的ThreadLocalMap的Entry(以下简写e),如果e不为null,则直接返回该Entry的value;
4、如果map为null或者e为null,返回setInitialValue()的值。setInitialValue()调用重写的initialValue()返回新值(如果没有重写initialValue将返回默认值null),并将新值存入当前线程的ThreadLocalMap(如果当前线程没有ThreadLocalMap,会先创建一个)。
3.2、void set(T value)
为【当前线程】的【当前ThreadLocal】赋值(初始值or新值)。和setInitialValue相当相似,就不多分析了。
3.3、void remove()
获取当前线程的ThreadLocalMap,map不为空,则移除当前ThreadLocal作为key的键值对。
Note:
remove()移除当前线程的当前ThreadLocal数据(只是清空该key-value键值对),而且是立即移除,移除后,再调用get方法将重新调用initialValue方法初始化(除非在此期间调用了set方法赋值)。
3.4、static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier)
JDK8新增,支持Lambda表达式,和ThreadLocal重写的initialValue()效果一样。
可以看出,withInitial()方法的入参是函数式接口Supplier,返回值是JDK8新增的内部类SuppliedThreadLocal,正如2.2所说,区别仅在于支持Lambda表达式赋值而已。使用事例如下:
Note:
·withInitial(supplier)是有返回值ThreadLocal的,So实例化时需将其赋值给ThreadLocal实例。
4、图解ThreadLocal
每个线程可能有多个ThreadLocal,同一线程的各个ThreadLocal存放于同一个ThreadLocalMap中。
图解ThreadLocal(JDK8).vsdx原图下载地址:https://github.com/zxiaofan/JDK-Study/tree/master/src/java1/lang/threadLocal
5、ThreadLocal-ThreadLocalMap源码分析
5.1、Entry getEntry(ThreadLocal<?> key)
首先来看get方法,你会发现ThreadLocalMap的get方法和传统Map不同,其返回的不是key-value的value,而是整个entry,当时entry的key是ThreadLocal,value是存放的值,这点是一致的。
a、getEntry源码分析:
getEnrty方法只会处理key被直接命中的entry,没有直接命中的(key冲突的)数据将调用getEntryAfterMiss()方法返回对应enrty,按照源码解释,这样做是为了尽可能提升直接命中的性能。
ThreadLocalMap之getEntry的流程:
1、计算Entry数组的index((length - 1) & key.hash)。
索引计算和HashMap的异同:
①相似之处:计算方式相同,均为(length - 1) & key.hash;length均为底层结构的大小(是大小,不是实际size)。
②不同之处:HashMap(JDK8)底层数据结构是位桶+链表/红黑树,而ThreadLocalMap底层数据结构是Entry数组;HashMap的key.hash的计算方式是native、异或、无符号位移,(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16),ThreadLocalMap的key.hash从ThreadLocal实例化时便由nextHashCode()确定。
2、获取对应index的节点Entry;
3、如果返回节点entry 有值且其key未冲突(只有1个即entry返回的key等于传入的key),则直接返回该entry;
4、返回entry为空或键冲突,则调用getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e)方法返回entry。
b、getEntryAfterMiss源码分析:
getEntryAfterMiss处理那些getEntry时没有被命中的key(value为空的直接返回null,so更确切的说是命中且有冲突的key)。入参是当前ThreadLocal,key在数组的索引index,以及index对应的键值对。
ThreadLocalMap之getEntryAfterMiss的流程:
仅分析Entry不为空的情况,
1、获取entry的key;
2、如果key一致(内存地址=判断),则返回该entry;
3、如果key为null,则调用expungeStaleEntry方法擦除该entry;
4、其他情况则通过nextIndex方法获取下一个索引位置index;
5、获取新index处的entry,再死循环2/3/4,直到定位到该key返回entry或者返回null。
c、expungeStaleEntry源码分析:
只要key为null均会被擦除,使得对应value没有被引用,方便回收。
5.2、set(ThreadLocal<?> key, Object value)
rehash():
size:table的实际entry数量;扩容阈值threshold:table.lenrth(默认16)大小的2/3;
首先调用expungeStaleEntries删除所有过期数据,如果清理数据后size>=threshold的3/4,则2倍扩容。
ps:阈yù值又叫临界值,是指一个效应能够产生的最低值或最高值。阀fá 控制、开关、把持。
ThreadLocalMap和HashMap在hash冲突时的解决方案对比:
HashMap:若冲突则将新数据按链表或红黑树逻辑插入。
详见《HashMap源码分析(jdk1.8)》
put(K key, V value)的逻辑:
1、判断键值对数组tab[]是否为空或为null,是则resize();
2、根据键值key的hashCode()计算hash值得到当前Node的索引i(bucketIndex),如果tab[i]==null【没碰撞】,直接新建节点添加,否则【碰撞】转入3
3、判断当前数组中处理hash冲突的方式为红黑树还是链表(check第一个节点类型即可),分别处理。【①是红黑树则按红黑树逻辑插入;②是链表,则遍历链表,看是否有key相同的节点;③有则更新value值,没有则新建节点,此时若链表数量大于阀值8【9个】,则调用treeifyBin方法(此方法先判断table是否为null或tab.length小于64,是则执行resize操作,否则才将链表改为红黑树)。】
4、如果size+1> threshold则resize。
ThreadLocalMap:
1、若指定位置index已有数据entry,逐个遍历entry:
1.1、若index处key相同,则更新value;
1.2、若index处key为null,则调用replaceStaleEntry清理过期数据并插入新数据(从index处挨个遍历,直到找到相同key更新value结束,或者一直未找到,则在index处放入new Entry)。replaceStaleEntry遍历时会将entry逐个后移,也就是说set进去的最新entry一定会放在index处,方便get时直接命中。
2、index处无数据,则放入新entry;随后清理过期数据并判断是否2倍扩容(size>=threshold的3/4)。
参考资料:
http://www.cnblogs.com/dolphin0520/p/3920407.html
有任何问题,欢迎指正探讨。
阅读全文
0 0
- Java并发编程-ThreadLocal
- java并发编程---ThreadLocal
- Java并发编程:ThreadLocal
- Java并发编程:ThreadLocal
- java并发编程实践 ThreadLocal
- java并发编程实战 ThreadLocal
- Java并发编程—ThreadLocal
- Java 并发编程--ThreadLocal类
- Java并发编程基础知识ThreadLocal
- 【Java并发编程实践】— ThreadLocal分析
- 【Java并发编程实践】— ThreadLocal分析
- Java并发编程之ThreadLocal类详解
- Java并发编程:深入剖析ThreadLocal
- Java并发编程:深入剖析ThreadLocal
- Java并发编程:深入剖析ThreadLocal
- Java并发编程:深入剖析ThreadLocal
- Java并发编程:深入剖析ThreadLocal
- Java并发编程:深入剖析ThreadLocal
- kmp再理解
- JSP中文乱码问题已经解决方法
- 在控制台上输入菱形的边长,打印出菱形
- VINS理论与代码详解3——IMU预积分
- 把可执行jar添加到桌面右键
- Java并发编程:ThreadLocal
- selenium+phantomjs的坑
- C#中Hashtable使用大全
- C# 怎么判断一个窗体打开
- 关联、聚合、组合、依赖、泛化
- Vuejs实践--事件绑定
- meta format-dection
- int argc, char* argv[]
- angularjs select标签使用ng-repeat标签之后,ng-model无法更新问题解决