作者 | 蔡柱梁
成都创新互联从2013年开始,是专业互联网技术服务公司,拥有项目成都网站设计、网站建设网站策划,项目实施与项目整合能力。我们以让每一个梦想脱颖而出为使命,1280元延长做网站,已为上家服务,为延长各地企业和个人服务,联系电话:028-86922220
审校 | 重楼
很多 Java 开发一般都是做中台较多,并发编程使用的不多。因此,对 ThreadLocal 不太熟悉,所以笔者这里想让大家了解它,知道它是用来干什么的。
ThreadLocal 是 Java 中一种线程封闭技术,它提供了一种线程本地变量的机制,使得每个线程都拥有一个独立的变量副本,这样可以避免多个线程访问同一个变量时产生的并发问题。
ThreadLocal 在工作中还是蛮常用的,笔者使用到的一些场景如下:
总的来说,当你需要和线程绑定的变量时,就可以考虑使用 ThreadLocal 啦!
至于线程安全问题,大家不妨想想我们平常说线程安全问题都是出现在什么场景?同一时间有两个或两个以上的线程对同一个变量进行修改,才有可能出现线程安全问题。但是使用 ThreadLocal,每个线程是独享自己的变量副本的,哪里还有线程安全问题呢?
这个上网一搜一大堆,笔者就说下注意事项好了,用完后一定要释放,避免内存泄漏,提供几个点给大家参考:
总之,要正确使用 ThreadLocal 并避免内存泄漏问题,需要注意适时清理、使用弱引用、避免存储过多数据、及时释放资源,并在使用线程池时特别小心。
下面是一个简单的示例代码:
public class ThreadLocalExample { private static final ThreadLocal threadLocal = new ThreadLocal<>(); public static void main(String[] args) { Thread workerThread = new Thread(() -> { try { // 在线程中设置ThreadLocal值 threadLocal.set(new Object()); // 执行业务逻辑 // ... } finally { // 在线程结束时清理ThreadLocal值 threadLocal.remove(); } }); workerThread.start(); // 等待线程结束 try { workerThread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } 在示例代码中,线程 workerThread 和 ThreadLocal 实例是一个怎样的关系呢?set 方法和 remove 方法都做了什么呢?为什么会有内存泄漏的情况呢?我们带着疑问一起往下看。4.1 java.lang.ThreadLocal#set我们直接从源码开始分析 ThreadLocal。public void set(T value) { // 获取当前线程 Thread t = Thread.currentThread(); // 通过当前线程获取ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } static class Entry extends WeakReference> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } } 结合示例代码来看,这里是当前线程A在 main 方法中通过 threadLocal 实例调用 threadLocal.set 方法,而 set 方法会给当前线程创建一个 ThreadLocalMap(如果没有的话),并使用 threadLocal 实例作为 key。它们的关系如下图:4.2 内存泄漏问题这里应该分成两种情况看:无线程复用和有线程复用。 无线程复用当 workerThread 结束后,没有强引用的 ThreadLocalMap 自然而然也会被垃圾回收器回收,不会出现内存泄漏。有线程复用这里也要分开看,有释放和无释放的情况。如果发生内存泄漏,当然就是我们没有释放导致的(释放可以通过调用 set、get、remove方法释放)。当我们使用线程池,线程会被复用时,ThreadLocalMap 的生命周期与它绑定的线程是一样的,所以不会被回收。如果这时发生了 gc,那么 Entry 的 key 是弱引用,key 会变成 null,而 value 将继续存活。如果该线程一直不调用 set/get/remove 方法,那么 value 一直得不到释放,就会发生内存泄漏的现象。那为什么使用 set/get/remove 可以避免内存泄漏呢?因为 set/get 在根据当前线程找到对应 Entry 元素后(这里是刚好是碰到了 key==null 的 entry[i],碰不到是不会顺手释放旧 value 的。因此,最好还是使用完后调用 remove 释放),发现 key == null,就会调用java.lang.ThreadLocal.ThreadLocalMap#expungeStaleEntry 释放引用,所以就不会发生内存泄漏了。这里就不再展示源码了,有兴趣的可以自己去看下。五、哈希冲突问题 上面看到 ThreadLocalMap 使用了 Hash,是不是马上就想到了哈希冲突呢?HashMap 遇到哈希冲突,在 key 不相同的情况下,会使用链表解决。但是 ThreadLocalMap 的 Entry 没有 next 指针,因此它明显不会采用链表,那么它是如何解决哈希冲突的呢?请看 java.lang.ThreadLocal.ThreadLocalMap#set 源码,笔者添加了注释,可以看到是怎么解决哈希冲突的。private void set(ThreadLocal key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; // 存在哈希冲突的话,会往下走,如果超过数组长度,就会回到0 e = tab[i = nextIndex(i, len)]) { ThreadLocal k = e.get(); if (k == key) { // 找到存储自己的entry,更新value e.value = value; return; } if (k == null) { // 因为 gc 导致 key 被回收了,这个 Entry 会被新的 Entry 取代(新的Entry的key和value就是这里的传参),旧的会被释放 replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }总结到这里相信大家对 ThreadLocal 都有了一定的了解。有什么想交流可以留言或私信笔者。作者介绍蔡柱梁,社区编辑,从事Java后端开发8年,做过传统项目广电BOSS系统,后投身互联网电商,负责过订单,TMS,中间件等。 网站名称:了解ThreadLocal,这一篇文章就够了 文章分享:http://www.gawzjz.com/qtweb/news45/194795.html 网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等 广告 声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联 猜你还喜欢下面的内容 linux和ubuntu有哪些区别「linux和ubuntu有什么区别」 Vue.use(ElementUI) 做了什么? 混合型卷积的未来发展前景 浏览器访问限制怎么解开?(域名还能解吗可以解锁) 免费令你轻松体验商用Linux系统(免费商用的linux系统) 数据库空值的含义和作用简述(数据库空值的作用) VPS有什么用途?美国服务器vps 主机域名中哪个表示主机名?() 黑龙江vps租用怎么搭建网站的 建站公司知识 企业网页怎么做 Redis页表的全面解析(Redis页表) CentOS6系升级Python2.7版本 如何在Java中扫描和验证图像上传 给本地单机架设Redis,实现数据库快速存取(单机版redis) 我们究竟需要学习哪些Android知识?知识图谱 asla是什么服务器?LA什么服务器 如何搭建云服务器?(云服务器怎么开发票) Shelllet命令:对整数进行数学运算 阿里云发布研发协同云推动百万企业开发运维一体化 云服务器便宜租用怎么搭建网站(云服务器可以自己搭建吗) SQLServer2000之设备激活错误的解析 alibaba+nacos权限认证绕过怎么办(三重权限认证卡) rpg游戏怎么做?(怎么造游戏) 创新互联数据库教程:MySQL修改存储过程(ALTERPROCEDURE) 同城分类信息 公路钻孔机 汽车玻璃修复 边坡防护网 纱窗 玻璃钢坐凳 垃圾桶 葡萄架 食品包装袋 石笼网 iso认证 茶楼设计 茶艺设计 三维植被网 PE包装袋 餐厅设计 发电机维修 成都定制网站建设 网络推广外包 成都创新互联 深圳惠宜防蚊纱窗 成都主机托管 上市企业网站建设方案 重庆移动网站建设 温江网站建设 广元双南建站 大英泰恒网站 雕琢时光 h5页面制作 亮美家 成都能净科技 成都多线服务器托管 SSL证书 正泰动物制药 雨棚定制 成都西信服务器托管 四川主机托管
在示例代码中,线程 workerThread 和 ThreadLocal 实例是一个怎样的关系呢?set 方法和 remove 方法都做了什么呢?为什么会有内存泄漏的情况呢?我们带着疑问一起往下看。
我们直接从源码开始分析 ThreadLocal。
public void set(T value) { // 获取当前线程 Thread t = Thread.currentThread(); // 通过当前线程获取ThreadLocalMap ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { table = new Entry[INITIAL_CAPACITY]; int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); table[i] = new Entry(firstKey, firstValue); size = 1; setThreshold(INITIAL_CAPACITY); } static class Entry extends WeakReference> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) { super(k); value = v; } }
结合示例代码来看,这里是当前线程A在 main 方法中通过 threadLocal 实例调用 threadLocal.set 方法,而 set 方法会给当前线程创建一个 ThreadLocalMap(如果没有的话),并使用 threadLocal 实例作为 key。
它们的关系如下图:
这里应该分成两种情况看:无线程复用和有线程复用。
那为什么使用 set/get/remove 可以避免内存泄漏呢?因为 set/get 在根据当前线程找到对应 Entry 元素后(这里是刚好是碰到了 key==null 的 entry[i],碰不到是不会顺手释放旧 value 的。因此,最好还是使用完后调用 remove 释放),发现 key == null,就会调用java.lang.ThreadLocal.ThreadLocalMap#expungeStaleEntry 释放引用,所以就不会发生内存泄漏了。这里就不再展示源码了,有兴趣的可以自己去看下。
上面看到 ThreadLocalMap 使用了 Hash,是不是马上就想到了哈希冲突呢?HashMap 遇到哈希冲突,在 key 不相同的情况下,会使用链表解决。但是 ThreadLocalMap 的 Entry 没有 next 指针,因此它明显不会采用链表,那么它是如何解决哈希冲突的呢?
请看 java.lang.ThreadLocal.ThreadLocalMap#set 源码,笔者添加了注释,可以看到是怎么解决哈希冲突的。
private void set(ThreadLocal key, Object value) { // We don't use a fast path as with get() because it is at // least as common to use set() to create new entries as // it is to replace existing ones, in which case, a fast // path would fail more often than not. Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); for (Entry e = tab[i]; e != null; // 存在哈希冲突的话,会往下走,如果超过数组长度,就会回到0 e = tab[i = nextIndex(i, len)]) { ThreadLocal k = e.get(); if (k == key) { // 找到存储自己的entry,更新value e.value = value; return; } if (k == null) { // 因为 gc 导致 key 被回收了,这个 Entry 会被新的 Entry 取代(新的Entry的key和value就是这里的传参),旧的会被释放 replaceStaleEntry(key, value, i); return; } } tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
到这里相信大家对 ThreadLocal 都有了一定的了解。有什么想交流可以留言或私信笔者。
蔡柱梁,社区编辑,从事Java后端开发8年,做过传统项目广电BOSS系统,后投身互联网电商,负责过订单,TMS,中间件等。
网站名称:了解ThreadLocal,这一篇文章就够了 文章分享:http://www.gawzjz.com/qtweb/news45/194795.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
广告
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联
猜你还喜欢下面的内容
建站公司知识
同城分类信息