揭秘ThreadLocal
ThreadLocal是开发中最常用的技术之一,也是面试重要的考点。本文将由浅入深,介绍ThreadLocal的使用方式、实现原理、内存泄漏问题以及使用场景。
ThreadLocal作用
在并发编程中时常有这样一种需求:每条线程都需要存取一个同名变量,但每条线程中该变量的值均不相同。
如果是你,该如何实现上述功能?常规的思路如下: 使用一个线程共享的Map<Thread,Object>
,Map中的key为线程对象,value即为需要存储的值。那么,我们只需要通过map.get(Thread.currentThread())
即可获取本线程中该变量的值。
这种方式确实可以实现我们的需求,但它有何缺点呢?——答案就是:需要同步,效率低!
由于这个map对象需要被所有线程共享,因此需要加锁来保证线程安全性。当然我们可以使用java.util.concurrent.*
包下的ConcurrentHashMap
提高并发效率,但这种方法只能降低锁的粒度,不能从根本上避免同步锁。而JDK提供的ThreadLocal
就能很好地解决这一问题。下面来看看ThreadLocal是如何高效地实现这一需求的。
如何使用ThreadLocal
在介绍ThreadLocal原理之前,首先简单介绍一下它的使用方法。
public class Main{private ThreadLocal<Integer> threadLocal = new ThreadLocal<>();public void start() {for (int i=0; i<10; i++) {new Thread(new Runnable(){@overridepublic void run(){threadLocal.set(i);threadLocal.get();threadLocal.remove();}}).start();}}
}
复制代码
- 首先我们需要创建一个线程共享的ThreadLocal对象,该对象用于存储Integer类型的值;
- 然后在每条线程中可以通过如下方法操作ThreadLocal:
set(obj)
:向当前线程中存储数据get()
:获取当前线程中的数据remove()
:删除当前线程中的数据
ThreadLocal的使用方法非常简单,关键在于它背后的实现原理。回到上面的问题:ThreadLocal究竟是如何避免同步锁,从而保证读写的高效?
ThreadLocal实现原理
ThreadLocal的内部结构如下图所示:
ThreadLocal
并不维护ThreadLocalMap
,并不是一个存储数据的容器,它只是相当于一个工具包,提供了操作该容器的方法,如get、set、remove等。而ThreadLocal
内部类ThreadLocalMap
才是存储数据的容器,并且该容器由Thread
维护。
每一个Thread
对象均含有一个ThreadLocalMap
类型的成员变量threadLocals
,它存储本线程中所有ThreadLocal对象及其对应的值。
ThreadLocalMap
由一个个Entry
对象构成,Entry
的代码如下:
static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}
复制代码
Entry
继承自WeakReference<ThreadLocal<?>>
,一个Entry
由ThreadLocal
对象和Object
构成。由此可见,Entry
的key是ThreadLocal对象,并且是一个弱引用。当没指向key的强引用后,该key就会被垃圾收集器回收。
那么,ThreadLocal是如何工作的呢?下面来看set和get方法。
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}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();
}ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}
复制代码
当执行set方法时,ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,将值存储进ThreadLocalMap对象中。
get方法执行过程类似。ThreadLocal首先会获取当前线程对象,然后获取当前线程的ThreadLocalMap对象。再以当前ThreadLocal对象为key,获取对应的value。
由于每一条线程均含有各自私有的ThreadLocalMap容器,这些容器相互独立互不影响,因此不会存在线程安全性问题,从而也无需使用同步机制来保证多条线程访问容器的互斥性。
为何要使用弱引用?
对弱引用不了解的同学可以参考笔者的另一篇文章:http://blog.csdn.net/u010425776/article/details/50760053。
Java设计之初的一大宗旨就是——弱化指针。 Java设计者希望通过合理的设计简化编程,让程序员无需处理复杂的指针操作。然而指针是客观存在的,在目前的Java开发中也不可避免涉及到“指针操作”。如:
Object a = new Object();
复制代码
上述代码创建了一个强引用a,只要强引用存在,垃圾收集器是不会回收该对象的。如果该对象非常庞大,那么为了节约内存空间,在该对象使用完成后,我们需要手动拆除该强引用,如下面代码所示:
a = null;
复制代码
此时,指向该对象的强引用消除了,垃圾收集器便可以回收该对象。但在这个过程中,仍然需要程序员处理指针。为了弱化指针这一概念,弱引用便出现了,如下代码创建了一个Person类型的弱引用:
WeakReference<Person> wr = new WeakReference<Person>(new Person());
复制代码
此时程序员不用再关注指针,只要没有强引用指向Person对象,垃圾收集器每次运行都会自动将该对象释放。
那么,ThreadLocalMap中的key使用弱引用的原因也是如此。当一条线程中的ThreadLocal对象使用完毕,没有强引用指向它的时候,垃圾收集器就会自动回收这个Key,从而达到节约内存的目的。
那么,问题又来了——这会导致内存泄漏问题!
ThreadLocal的内存泄漏问题
在ThreadLocalMap中,只有key是弱引用,value仍然是一个强引用。当某一条线程中的ThreadLocal使用完毕,没有强引用指向它的时候,这个key指向的对象就会被垃圾收集器回收,从而这个key就变成了null;然而,此时value和value指向的对象之间仍然是强引用关系,只要这种关系不解除,value指向的对象永远不会被垃圾收集器回收,从而导致内存泄漏!
不过不用担心,ThreadLocal提供了这个问题的解决方案。
每次操作set、get、remove操作时,ThreadLocal都会将key为null的Entry删除,从而避免内存泄漏。
那么问题又来了,如果一个线程运行周期较长,而且将一个大对象放入LocalThreadMap后便不再调用set、get、remove方法,此时该仍然可能会导致内存泄漏。
这个问题确实存在,没办法通过ThreadLocal解决,而是需要程序员在完成ThreadLocal的使用后要养成手动调用remove的习惯,从而避免内存泄漏。
ThreadLocal的使用场景
Web系统Session的存储就是ThreadLocal一个典型的应用场景。
Web容器采用线程隔离的多线程模型,也就是每一个请求都会对应一条线程,线程之间相互隔离,没有共享数据。这样能够简化编程模型,程序员可以用单线程的思维开发这种多线程应用。
当请求到来时,可以将当前Session信息存储在ThreadLocal中,在请求处理过程中可以随时使用Session信息,每个请求之间的Session信息互不影响。当请求处理完成后通过remove方法将当前Session信息清除即可。
揭秘ThreadLocal相关推荐
- 2019年Java大厂面试(吐血超详细总结)
本文来自于慕课网手记: Java大厂面试(吐血超详细总结) 作者:小码哥的freestyle 链接: https://www.imooc.com/article/286545 来源:慕课网 面试清单 ...
- 转 :2019年Java大厂面试(吐血超详细总结)
2019年Java大厂面试(吐血超详细总结) 本文来自于慕课网手记:Java大厂面试(吐血超详细总结),转载请保留链接 ;) 转载自:https://www.imooc.com/article/286 ...
- ThreadLocal的短板,我TTL来补
之前我已经分析了ThreadLocal.InheritableThreadLocal.FastThreadLocal. 然后有小伙伴让我再说说TransmittableThreadLocal(下边统一 ...
- BAT这样的大公司为什么面试经常拿ThreadLocal考验求职者
什么是ThreadLocal ThreadLocal是一个本地线程副本变量工具类,各个线程都拥有一份线程私有的数据,线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用. ThreadLoca ...
- (Java多线程常见面试题)ThreadLocal 是什么?有哪些使⽤场景?
最近在研究多线程项目时,无意间看到一个很有意思的Java类----ThreadLocal.于是乎一向对于新东西充满好奇的我又开始了一系列深挖细究,在经过学习和参考网上其他大佬的见解后,现将自己的理解作 ...
- FastThreadLocal吞吐量居然是ThreadLocal的3倍
目前关于FastThreadLocal的很多文章都有点老有点过时了(本文将澄清几个误区),很多文章关于FastThreadLocal介绍的也不全,希望本篇文章可以带你彻底理解FastThreadLoc ...
- 康复治疗学可以考计算机吗,【大揭秘】2018“人机对话”康复医学治疗技术专业技术资格考试...
原标题:[大揭秘]2018"人机对话"康复医学治疗技术专业技术资格考试 昨天,关于"2018年康复医学治疗技术专业技术资格考试采用人机对话考试方式"的通知一经发 ...
- 正确理解ThreadLocal
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt107 首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的, ...
- 零代价修复海量服务器的内核缺陷——UCloud内核热补丁技术揭秘
下述为UCloud资深工程师邱模炯在InfoQ架构师峰会上的演讲--<UCloud云平台的内核实践>中非常受关注的内核热补丁技术的一部分.给大家揭开了UCloud云平台内核技术的神秘面纱. ...
最新文章
- 带边框有点击事件的表格
- linux降内核版本_ubuntu18.04 降内核版本的问题
- Oracle怎样创建共享文件夹,Oracle vm要如何使用共享文件夹的解决方法
- 机器学习(MACHINE LEARNING) 【周志华版-”西瓜书“-笔记】 DAY5-神经网络
- 树网的核(codevs 1167)
- Cython入门.VS.C++
- 误人子弟的网络,谈谈HTTP协议中的短轮询、长轮询、长连接和短连接(转载)
- 如何设计一个高可用的运营系统
- Java-IO-对接流
- 单例模式——饿汉式和懒汉式
- 二叉树数据结构和算法
- Python机器学习:多项式回归与模型泛化003过拟合与欠拟合
- es6,es7,es8语法总结
- 不用计算机怎么连接无线,不用电脑可以装wifi吗 不通过电脑装wifi方法【图文】...
- Could not resolve type alias 解决方法
- python机械臂仿真_机械臂 python
- VS2010在加载项目时,提示无法打开项目文件, 此安装不支持该项目类型的解决方法
- 如何诊断SQL数据?
- Linux引导故障和修复进入系统
- android 是否可触摸,android 2.0可能支持多点触摸?