ThreadLocal 和 InheritableThreadLocal
在学习ThreadLocal之前,建议先了解Java中的4种引用
一、先看一下Thread,ThreadMap,ThreadLocal的关系
Thread中持有一个ThreadLocalMap
,这里你可以简单理解为就是持有一个数组,这个数组是Entry 类型的。 Entry 的key 是ThreadLocal 类型的(准确说是一个指向ThreadLocal的引用)
,value 是Object 类型
。也就是一个ThreadLocalMap 可以持有多个ThreadLocal
。他们是一对多的关系
同时通过源码我们可以看到,ThreadLocalMap是ThreadLocal里的一个静态内部类,Entry又是ThreadLocalMap的一个静态内部类
。这里可以思考一下为何不直接使用HashMap?(HashMap节点之间是通过强引用关联)
这里明明确一点:ThreadLocal不是隶属于Thread的,他是一个独立的(一个用于操作Thread中threadLocals属性的工具类),使用他的时候,必须new出来,而不是通过Thread来获得
,明确这点,我感觉理解起来会好很多。
static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;//可以看到Thread的threadLocals属性底层是一个//由ThreadLocal类作为K,Object类作为v的Map结构Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}
- ThreadLocal的作用:
为共享变量在每一个线程中创建一个副本,以保证不同的线程中拿到该变量的值都是不一样的
二、ThreadLocal的基本方法
在实际使用之前我们先来了解一下ThreadLocal的几个常用方法void
- set(Object value)设置当前线程的线程局部变量的值。
public void set(T value) {Thread t = Thread.currentThread();//获取当前执行的线程ThreadLocalMap map = getMap(t); //获得当前线程的ThreadLocalMap实例if (map != null)//如果map不为空,说明当前线程已经有了一个ThreadLocalMap实例//注意这个方法,底层会覆盖同一个k的v,也就是每一个Thread中的某个ThreaLocal的可以重复设置,但会覆盖前面的值map.set(this, value);//直接将当前value设置到ThreadLocalMap中elsecreateMap(t, value); //说明当前线程是第一次使用线程本地变量,构造map
}
- 该方法返回当前线程所对应的线程局部变量。
public T get() {Thread t = Thread.currentThread();//获得当前线程的ThreadLocalMap实例ThreadLocalMap map = getMap(t);if (map != null) {//查询当前ThreadLocal变量实例对应的EntryThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")//返回对应Entry的valueT result = (T)e.value;return result;}}//如果map为null,即还没有初始化,走初始化方法return setInitialValue();}
--------------------------------------------------------private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.refersTo(key))return e;elsereturn getEntryAfterMiss(key, i, e);}
-----------------------------------------------------------------
private T setInitialValue() {T value = initialValue();//protected方法,用户可以重写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);}if (this instanceof TerminatingThreadLocal) {TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);}return value;
}
- public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null) {//根据当前ThreadLocal变量删除Thread.ThreadLocals中对应的值m.remove(this);}}
- protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次
。ThreadLocal中的缺省实现直接返回一个null。
三、引用ThreadLocal时示意图
代码:
/*** @author Cristianoxm*/
public class TestThreadLocal
{static ThreadLocal<String> threadLocal1=new ThreadLocal<String>();static ThreadLocal<Integer> threadLocal2=new ThreadLocal<Integer>();public static void main(String[] args) {Thread [] runs=new Thread[3];for (int i=0;i<runs.length;i++){new MyThread(i).start();}}public static class MyThread extends Thread{int id;public MyThread(int id){this.id=id;}@Overridesynchronized public void run(){threadLocal1.set("线程-"+id);if(id == 1){threadLocal2.set(1);}System.out.println(Thread.currentThread().getName()+"编号:"+threadLocal1.get());System.out.println(threadLocal2.get());}}
}
内存图大概如下:只有Thread-1中有指向Threadlocal2的Entry,thread-2中是没有的
四、底层原理解析
掌握了基本用法之后,我们就来聊聊ThreadLocal 的底层原理吧。在java中每一个线程都会有一个 ThrealLocalMap ,里面存放了一个一个的键值对(我们一般称之为Entry),键值对的key 就是 Threadlocal本身,而value 就是他的值
,正如下图所示。
我们仔细的看一下这张图。左边是stack(栈),栈里面有用户线程和threadlocal的引用
。而在堆里,threadlocal 用一个弱引用指向了我们ThrealLocalMap 的key,而用户线程 则是一个强引用指向了 这个enrty的 value.现在我们就能理解为什么threadlocal 的副本是怎么回事了,线程拷贝一份值,用Threadlocal自身做key ,保存在一个map中,从而实现了每个线程之间变量的互相隔离
。
五、内存泄露分析
提到Threadlocal 必然不得不说他的内存泄露问题,首先我们注意下,threadlocalmap 是一个弱引用,也就是说只要gc,我们上面这条 ThreadLocal->key 的虚线就回被打断,但是如果我们的当前线程没有结束,那么指向value的强引用自然不会断开。
于是当gc发生的时候,我们的map中就出现了一个,key为null的 entry,但是这个entry 由于value的强引用并不会被回收,那么map中就出现了一个永远访问不到的entry!内存泄漏由此产生
。当然,threadLocal 对此还是有对策的,当我们调用 get set 和 remove 方法的时候,threadlocal都会去检查是否有这样的entry并把他们清除掉。
但是注意,除了 remove方法,其他两个方法都是不可靠的
,他们并非及时同时不一定会去执行清除方法。
- 对象引用图:
所以为了防止内存泄露,我们必须在使用完Threadlocal后及时的去调用remove方法!所以:ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用
。想要避免内存泄露就要手动remove()掉!
- 那么:为什么使用弱引用
说道这里可能很多人会问了,为什么threadlocal要采用弱引用,采用强引用不是就没有这个访问不到entry的毛病了吗。那我们这里就假设一下,如果这里的key是强引用的话,如果我们给这个Threadlocal 赋值为null。那么情况就如下图所示
请大家注意这里 指向我们value的线已经断了,照理来说这个entry应该被回收,但是由于我们的key是强引用,所以这个entry就变得和 Threadlocal同生共死了
,从而造成内存泄露!也即是:entry中的value置空,threadlocal置空,那么这个空的entry会因为key的强引用,变得和 Threadlocal同生共死,就内存泄露了
- 总结使用ThreadLocal时会发生内存泄漏的前提条件:
- ①ThreadLocal引用被设置为null,且后面没有set,get,remove操作。
- ②线程一直运行,不停止。(线程池)
- ③触发了垃圾回收。(Minor GC或Full GC)
我们看到ThreadLocal出现内存泄漏条件还是很苛刻的,所以我们只要破坏其中一个条件就可以避免内存泄漏,单但为了更好的避免这种情况的发生我们使用ThreadLocal时遵守以下两个小原则
- ①
ThreadLocal申明为private static final。
Private与final 尽可能不让他人修改变更引用,
Static 表示为类属性,只有在程序结束才会被回收。- ②
ThreadLocal使用后务必调用remove方法
。
最简单有效的方法是使用后将其移除。
六、流程图
七、ThreadLocal的应用
- 管理Connection
最典型的是管理数据库的Connection
:当时在学JDBC的时候,为了方便操作写了一个简单数据库连接池,需要数据库连接池的理由也很简单,频繁创建和关闭Connection是一件非常耗费资源的操作,因此需要创建数据库连接池
,那么,数据库连接池的连接怎么管理呢??我们交由ThreadLocal来进行管理。为什么交给它来管理呢??ThreadLocal能够实现当前线程的操作都是用同一个Connection,保证了事务
!
public class DBUtil {//数据库连接池private static BasicDataSource source;//为不同的线程管理连接private static ThreadLocal<Connection> local;static {try {//加载配置文件Properties properties = new Properties();//获取读取流InputStream stream = DBUtil.class.getClassLoader().getResourceAsStream("连接池/config.properties");//从配置文件中读取数据properties.load(stream);//关闭流stream.close();//初始化连接池source = new BasicDataSource();//设置驱动source.setDriverClassName(properties.getProperty("driver"));//设置urlsource.setUrl(properties.getProperty("url"));//设置用户名source.setUsername(properties.getProperty("user"));//设置密码source.setPassword(properties.getProperty("pwd"));//设置初始连接数量source.setInitialSize(Integer.parseInt(properties.getProperty("initsize")));//设置最大的连接数量source.setMaxActive(Integer.parseInt(properties.getProperty("maxactive")));//设置最长的等待时间source.setMaxWait(Integer.parseInt(properties.getProperty("maxwait")));//设置最小空闲数source.setMinIdle(Integer.parseInt(properties.getProperty("minidle")));//初始化线程本地local = new ThreadLocal<>();} catch (IOException e) {e.printStackTrace();}}public static Connection getConnection() throws SQLException {if(local.get()!=null){return local.get();}else{//获取Connection对象Connection connection = source.getConnection();//把Connection放进ThreadLocal里面local.set(connection);//返回Connection对象return connection;}}//关闭数据库连接public static void closeConnection() {//从线程中拿到Connection对象Connection connection = local.get();try {if (connection != null) {//恢复连接为自动提交connection.setAutoCommit(true);//这里不是真的把连接关了,只是将该连接归还给连接池connection.close();//既然连接已经归还给连接池了,ThreadLocal保存的Connction对象也已经没用了local.remove();}} catch (SQLException e) {e.printStackTrace();}}
}
同样的,Hibernate对Connection的管理也是采用了相同的手法(使用ThreadLocal,当然了Hibernate的实现是更强大的
)
八、InheritableThreadLocal
InheritableThreadLocal类其实是重写了ThreadLocal的3个函数:
/*** 该函数在父线程创建子线程,向子线程复制InheritableThreadLocal变量时使用*/protected T childValue(T parentValue) {return parentValue;}/*** 由于重写了getMap,操作InheritableThreadLocal时,* 将只影响Thread类中的inheritableThreadLocals变量,* 与threadLocals变量不再有关系*/ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}/*** 类似于getMap,操作InheritableThreadLocal时,* 将只影响Thread类中的inheritableThreadLocals变量,* 与threadLocals变量不再有关系*/void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);}
- 作用:我们知道:当前线程的ThreadLocalMap,主要存储该线程自身的ThreadLocal。而
InheritableThreadLocal,是自父线程集成而来的ThreadLocalMap,主要用于父子线程间ThreadLocal变量的传递
。 即主要存储可自动向子线程中传递的ThreadLocal.ThreadLocalMap
九、inheritThreadLocals的使用
- thread的创建
Thread thread = new Thread();
----------------------------------------------
public Thread() {init(null, null, "Thread-" + nextThreadNum(), 0);}
/*** 默认情况下,设置inheritThreadLocals可传递*/private void init(ThreadGroup g, Runnable target, String name,long stackSize) {init(g, target, name, stackSize, null, true);}
/*** 初始化一个线程.* 此函数有两处调用,* 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);......(其他代码)}
可以看到,采用默认方式产生子线程时,inheritThreadLocals=true;若此时父线程inheritableThreadLocals不为空,则将父线程inheritableThreadLocals传递至子线程。
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);}
/*** 构建一个包含所有parentMap中Inheritable ThreadLocals的ThreadLocalMap,该函数只被 createInheritedMap() 调用.*/private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);// ThreadLocalMap 使用 Entry[] table 存储ThreadLocaltable = new Entry[len];// 逐一复制 parentMap 的记录for (int j = 0; j < len; j++) {Entry e = parentTable[j];if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {// 可能会有同学好奇此处为何使用childValue,而不是直接赋值,// 毕竟childValue内部也是直接将e.value返回;// 个人理解,主要为了减轻阅读代码的难度Object value = key.childValue(e.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++;}}}}
从ThreadLocalMap可知,子线程将parentMap中的所有记录逐一复制至自身线程。
- 代码例子
public class TestInheritableThreadLocal {public static ThreadLocal<String> threadLocal = new ThreadLocal<>();public static ThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();public static String get() {return threadLocal.get();}public static void set(String value) {threadLocal.set(value);}public static String inheritableThreadLocalGet() {return inheritableThreadLocal.get();}public static void inheritableThreadLocalSet(String value) {inheritableThreadLocal.set(value);}public static void main(String[] args) {for (int i = 0; i < 5; i++) {TestInheritableThreadLocal.set("ye");Thread t = new Thread(() -> System.out.println(Thread.currentThread().getName() + ":" + TestInheritableThreadLocal.get()));t.start();}for (int i = 0; i < 5; i++) {TestInheritableThreadLocal.inheritableThreadLocalSet("ye");Thread t = new Thread(() -> System.out.println(Thread.currentThread().getName() + ":" + TestInheritableThreadLocal.inheritableThreadLocalGet()));t.start();}}
}
参考文章
参考文章
ThreadLocal 和 InheritableThreadLocal相关推荐
- 每日一博 - ThreadLocal VS InheritableThreadLocal VS TransmittableThreadLocal
文章目录 ThreadLocal 核心API ThreadLocal类 源码分析 set get remove 缺陷 InheritableThreadLocal 源码解析 局限性 Transmitt ...
- 【高并发】ThreadLocal、InheritableThreadLocal
文章目录 1.概述 2.需要解决的问题 ThreadLocal InheritableThreadLocal 1.概述 转载:添加链接描述 2.需要解决的问题 我们还是以解决问题的方式来引出Threa ...
- ThreadLocal和InheritableThreadLocal使用
InheritableThreadLocal代码 public class InheritableThreadLocal<T> extends ThreadLocal<T> { ...
- 多线程-ThreadLocal,InheritableThreadLocal
ThreadLocal 变量值得共享可以使用public static变量的形式,所有的线程都使用同一个public static变量.如果想实现每一个线程都有自己的共享变量该如何解决呢?JDK中提供 ...
- 【多线程编程】--ThreadLocal、InheritableThreadLocal(ITL)、TransmittableThreadLocal(TTL)解析
目录 一.前言 二.ThreadLocal 2.1.为什么会用到ThreadLocal(ThreadLocal应用场景) 2.2.ThreadLocal实现原理 2.3.ThreadLocalMap ...
- ThreadLocal与InheritableThreadLocal源码解析
ThreadLocal 变量值的共享可以使用public static变量的形式实现,所有的线程都使用同一个public static变量,那如何实现每一个线程都有自己的变量呢?JDK提供的Threa ...
- JUC第六讲:ThreadLocal/InheritableThreadLocal详解/TTL-MDC日志上下文实践
本文是JUC第六讲:ThreadLocal/InheritableThreadLocal详解.ThreadLocal无论在项目开发还是面试中都会经常碰到,本文就 ThreadLocal 的使用.主要方 ...
- 【高并发】ThreadLocal学会了这些,你也能和面试官扯皮了!
来自:冰河技术 前言 我们都知道,在多线程环境下访问同一个共享变量,可能会出现线程安全的问题,为了保证线程安全,我们往往会在访问这个共享变量的时候加锁,以达到同步的效果,如下图所示. 对共享变量加锁虽 ...
- 获取返回值作为变量_解决多线程间共享变量线程安全问题的大杀器——ThreadLocal...
微信公众号:Zhongger 我是Zhongger,一个在互联网行业摸鱼写代码的打工人! 关注我,了解更多你不知道的[Java后端]打工技巧.职场经验等- 上一期,讲到了关于线程死锁.用户进程.用户线 ...
最新文章
- The Long-Term Stability of Ecosystems
- Gym 101334A	Area 51 数学
- FFmpeg--命令详解
- 【转载】正则表达式30分钟入门教程
- Python中的pip包管理工具被删除重新进行安装
- 用Python+Appium自动写网课考试
- Superset 实现可视化报表发布
- 在Word文档里如何快速返回目录页-Office学习
- 一款完整开源的物联网基础平台
- A12 屏幕旋转流程
- 论文中的 w.r.t. 和 i.e. 是什么意思
- Zoreto+坚果云+pdf expert(papership) 实现文献管理和批注同步
- 高德导航在天地图显示
- 新手建网站的步骤及注意事项
- 基于时间序列分析方法的零售业快消品销量预测研究
- Automated Phrase
- java如何截取视频文件_Java获取视频时长及截取帧截图详解
- MAML:Model-Agnostic Meta-Learning for Fast Adaptation of Deep Networks论文精读及详解
- 2016中国国际石墨烯创新大会展商名录抢先看
- 关于一粒云盘使用心得