hreadLocal,不少人都叫它作线程本地变量,也有些地方叫作线程本地存储,其实意思差很少。java
可能不少朋友都知道ThreadLocal为变量在每一个线程中都建立了一个副本,那样每一个线程能够访问本身内部的副本变量。程序员
这句话从表面上看起来理解正确,但实际上这种理解是不太正确的。下面咱们细细道来。数据库
多线程并发执行时,须要数据共享,所以才有了volatile变量解决 多线程间的数据可见性,安全
也有了锁的同步机制,使变量或代码块在某一时该,只能被一个线程访问,确保共享数据的正确性。(Synchronized用于线程间的数据共享的)性能优化
多线程并发执行时,并非全部数据都须要共享的,这些不须要共享的数据,让每一个线程去维护就OK了,ThreadLocal就是用于线程间的数据隔离的。多线程
给你们推荐一个程序员学习秋秋群:945622618。群里有分享的视频,还有思惟导图
群公告有视频,都是干货的,你能够下载来看。主要分享分布式架构、高可扩展、高性能、高并发、性能优化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战学习架构师视频。架构
深刻解析ThreadLocal类:并发
先咱们来看一下ThreadLocal类是如何为每一个线程建立一个变量的副本的。分布式
先看下get方法的实现:函数
第一句是取得当前线程,而后经过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。
而后接着下面获取到Entry键值对,注意这里获取Entry时参数传进去的是 this,即ThreadLocal实例,而不是当前线程t。若是获取成功,则返回value值。
若是map为空,则调用setInitialValue方法返回value。
接着看一下getMap方法中作了什么:
在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals,类型为ThreadLocalMap。
这里意味着每个线程都自带一个ThreadLocalMap成员变量。
继续取看ThreadLocalMap的实现:
能够看到ThreadLocalMap的Entry继承了WeakReference,而且使用ThreadLocal做为键值。
也就是说WeakReference封装了ThreadLocal,并做为了ThreadLocalMap的Entry的Key。
总结一下,在每一个线程Thread内部有一个ThreadLocalMap类型的成员变量threadLocals,
这个ThreadLocalMap成员变量的Entry的Key为,当前ThreadLocal变量的WeakReference封装,value为变量。
为什么ThreadLocalMap的键值为ThreadLocal对象? 由于每一个线程中可能须要有多个threadLocal变量,也就是ThreadLocalMap里面可能会有多个Entry。
在每一个线程内部 第一次调用ThreadLocal.get方法时,都会返回Null。由于默认状况下,initialValue方法返回的是null。
null 赋给(强转) 基本数据类型时会抛的空指针,null赋给 引用类型没问题。
能够在ThreadLocal的构造函数重写initialValue()方法。以下
ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){
protected Long initialValue() {
return Thread.currentThread().getId();
};
};
或者在调用ThreadLocal.get方法以前,须要先执行set(),以保证threadlocals中有值。
或者value为引用类型变量,null赋给 引用类型没问题。,以下,hibernate中典型的ThreadLocal的应用:
开篇说ThreadLocal建立副本 的说法是不太正确的。为何?
从上面这个hibernate的例子来看,这是一个使用ThreadLocal解决数据库链接的单例 在多线程中同时操做查询和关闭的状况。
首先这里面不是建立副本,而是分发新的内存地址(即,新的数据库链接的单例的内存地址),以当前ThreadLocal为key,value指向传入新的数据库链接的单例的内存地址。
从而达到单个线程获取数据链接的线程安全而已,也就是每一个线程都有一个独立的数据库链接的单例。
假设相反状况,一个数据库链接单例 若是在2个线程中被同时引用,2线程分别同一时间操做读取和close,确定会出现冲突。
因此须要减小每次new的开销仍是得使用数据库链接池。
ThreadLocal的内存泄露问题:
当使用线程池来复用线程时,一个线程使用完后并不会销毁线程,那么 分发的那个实例会一直绑定在这个线程上。
因为WeakReference封装了ThreadLocal,并做为了ThreadLocalMap的Entry的Key。若是在某些时候ThreadLocal对象被赋Null的话,弱引用会被GC收集,这样就会致使Entry的Value对象找不到,
线程被复用后若是有调用ThreadLocal.get/set方法的话,方法里面会去作遍历清除 以[ThreadLocal=Null ]为Key的Entry; 但若是一直没调用ThreadLocal.get/set方法的话就会致使内存泄漏了。
因此通常线程用完ThreadLocal后,要调用threadLocal.remove(); 以下
给你们推荐一个程序员学习秋秋群:945622618。群里有分享的视频,还有思惟导图
群公告有视频,都是干货的,你能够下载来看。主要分享分布式架构、高可扩展、高性能、高并发、性能优化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分布式项目实战学习架构师视频。