ThreadLocal原理解析以及是否需要调用remove方法

平常的开发过程中,如果有个类不是线程安全的,比如SimpleDateFormat,要使这个类在并发的过程中是线程安全的,那么可以将变量设置位局部变量,不过存在的问题就是频繁的创建对象,对性能和资源会有一定降低和消耗;那么这里就可以用到ThreadLocal作为线程隔离,那么ThreadLocal是如何实现线程与线程之间隔离的呢,待会儿会在下文进行讲解。

之前有篇使用ThreadLocal做线程之间的隔离实例,大家可以参考一下:使用ThreadLocal实现Mybatis多数据源

JDK引用类型

在了解ThreadLocal原理之前,先让大家了解一下JDK中的引用类型。

JDK共有四种引用类型:

1、强引用类型:就是平常创建的对象都属于强引用类型,比如 Object object = new Object();该object为强引用类型,如果该引用没有主动置为null,那么该引用的对象就不会被GC回收,所以一般在写完一段业务之后都会将用到的对象引用置为null,就是为了辅助GC更好的进行垃圾回收。

2、软引用类型:比强引用的类型弱一点,在应用程序发生OOM(内存溢出)之前就会去回收这些弱引用占用的内存,使用的SoftReference类,使用示例如下:

 3、弱引用类型:比软引用类型还要弱一点,在下一次发生GC回收之前就会被垃圾回收器进行回收,使用WeakReference类,使用示例如下:

4、虚引用类型:这个是JDK中最弱的引用类型,在对象被回收之前会移入到一个队列当中,然后在进行删除,这个引用类型用的不多,在JDK中引用的类是PhantomReference,这个需结合引用队列(ReferenceQueue)以及重写finalize方法进行使用,使用示例如下:

 

 对于应用场景来说,如果当前对象为可有可无的话,那么可以使用软引用或者弱引用进行使用,而对应虚引用的话,个人认为可以适用在非GC回收的区域(比如:元空间MetaSpace)使用,可以用来监测这些区域的回收情况等。

基本原理

ThreadLocal的用法呢,这里就先不谈;底层使用的是ThreadLocalMap这个Map的底层采用的是ThreadLocalMap.Entry,ThreadLocalMap这个类在每个线程当中都会存在一份对应的单独得对象,在Thread类中变量如下:

    /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

这里就是线程安全的重点,因为并发的情况下,每个线程都对应着有份自己的ThreadLocalMap,所以就不存在多线程竞争资源问题,所以如果使用ThreadLocal建议和线程一起使用,以为这样可以减少系统的性能开销以及对ThreadLocal对象的一种复用,提升系统性能。

看一下ThreadLocalMap,会发现Entry继承了WeakReference类,说明这个类创建出来的对象是弱引用对象

static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}
//容量
private static final int INITIAL_CAPACITY = 16;
//存储属性值
private Entry[] table;
//构造初始化table数组
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);
}

我们开看一下get方法:获取当前线程,然后通过调用getMap方法获取到当前线程的ThreadLocalMap对象,对于刚开始初始化的线程或者在ThreadLocalMap中没有找到来说,那么就会走下面的setInitialValue方法,如果已经初始化的ThreadLocalMap来说,会直接获取对应的Entry对象

    /*** Returns the value in the current thread's copy of this* thread-local variable.  If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local*/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();}

在setInitialValue方法中,会发现去获取初始化方法中的对象,如果在创建ThreadLocal没有重写初始化方法的话,那么就会返回null,或者在使用前调用set方法重新设置一下当前线程中的ThreadLocalMap中的属性初始化值,大家可以各自去看一下set方法;

private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);return value;
}

这里列一下getEntry方法,根据ThreadLocal对象计算出hash值找到对应的table数组的位置,并且获取到这个对象。

private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e);
}

问题

以为这就完了?并没有,规范的使用为在使用过get方法应该在调用remove方法进行删除(即将当前线程ThreadLocalMap中的引用置为空并且table数组中的Entry值也置为空);如果是线程池的话,那么这些数据就会一直存在,如果没有及时删除会造成内存泄漏。

调用remove方法回去执行ThreadLocalMap的remove方法,而在这个方法中,通过计算得到对应的Entry数组的位置,并且进行引用清除以及table数组清空,避免内存泄漏问题,

public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);
}
/*** Remove the entry for key.*/
private void remove(ThreadLocal<?> key) {Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {if (e.get() == key) {//将Entry本身的引用ThreadLocal置为空e.clear();//清空table数组中Entry.valueexpungeStaleEntry(i);return;}}
}

但是如果是经常性用到的ThreadLocal的话,个人建议可以不用删除,因为如果频繁使用的话,置为null,后面又会重新调用在ThreadLocalMap未找到,那么就会调用setInitialValue方法,重新创建对象并且赋值,在某种意义上可以说是和局部变量是一样的了,这样就违背了当初减少性能开销的需求了。

在加上Entry继承了WeakReference类,所以创建的对象会是个弱引用类型,在GC进行回收时候会被回收掉的,如果回收掉了引用对象,那么Entry中的value变量值是否还存在呢;

继续解析

注意这里是回收掉引用的对应,即ref.get()为null值,但是弱引用本身这个对象是还在的,我们看一下在setInitialValue方法中是如何处理的,重新设置里面,如果获取到ThreadLocal引用没有获取到,说明这个弱引用被回收了,这里就会去调用replaceStaleEntry方法。

private T setInitialValue() {T value = initialValue();Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);//重新设置值elsecreateMap(t, value);return value;
}
//ThreadLocalMapprivate void set(ThreadLocal<?> key, Object value) {Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();if (k == key) {e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();
}

 而在replaceStaleEntry方法中有个这么一行代码,将value变量置为null并且重新创建Entry对象,所以就算是没有调用remove删除方法,在GC过后依旧会置为null。

            // If key not found, put new entry in stale slot
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);

在此会有个小问题,为啥不用SoftReference而是使用WeakReference,个人觉得如果使用软引用的话,如果是使用线程池并且ThreadLocal会频繁访问的话,那么是可以的,但是实际应用并非只有这种情况,而且在发生OOM之前,只会回收掉软引用对象,但是Entry中的value变量还在,并不能真正的回收掉值,只有等到下一次使用的时候才能置为null,所以综合来看使用WeakReference还是最好的选择。

欢迎各位大佬一起讨论

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/537108.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Maven基础及概念

前言 今天就来聊聊Maven的基础和一些比较概念性的东西&#xff0c;还有一些常用的Maven命令啥的&#xff0c;主要是某人脑子记不住&#xff0c;记在博客中让她自己看吧&#xff0c;省的费心给她找。 后续的文章会聊到Maven的一些比较高级用法&#xff0c;像自定义插件&#x…

Maven之pom.xml常用标签解析及镜像配置

前言 Maven仅仅是个打包工具而已&#xff0c;个人觉得没有太大必要花费在打包工具上&#xff0c;这里就列举一下个人觉得会常用标签的使用就好了&#xff0c;原理啥的基本就不太会去深度了解了&#xff0c;如果以后遇到需了解Maven工作原理的工作的话&#xff0c;到时候一定分…

Maven高级之插件开发

前言 终于来到了Maven的插件开发&#xff0c;其实Maven的插件并没有想象的那么难&#xff0c;刚开始讲Maven基础的时候就演示了一下JDK是如何打包的&#xff0c;Maven打包只是在JDK打包上封装了一层而已&#xff0c;Maven也支持自定义插件开发 创建 我们先使用quickstart原型…

Maven高级之archetype(原型/骨架)开发

前言 archetype这个的主要功能就是将写好的项目模块打包成一个原型&#xff0c;然后提供给其他人使用&#xff0c;这样别人就可以快速使用这个项目模板了。 这个东西虽然很多人都基本用不上&#xff0c;但原型这个东西用的好还是很方便的&#xff0c;能够在开发新项目上省去大…

深度学习在搜索业务中的探索与实践

本文根据美团高级技术专家翟艺涛在2018 QCon全球软件开发大会上的演讲内容整理而成&#xff0c;内容有修改。引言 2018年12月31日&#xff0c;美团酒店单日入住间夜突破200万&#xff0c;再次创下行业的新纪录&#xff0c;而酒店搜索在其中起到了非常重要的作用。本文会首先介绍…

SpringBoot自动配置原理流程

前言 新公司太忙了&#xff0c;都没啥空更新博客&#xff0c;就随便记录一下以前的学习笔记吧。SpringBoot是基于Spring上的衍生框架&#xff0c;只要看懂了Spring的话&#xff0c;学这个就比较简单了&#xff1b;SpringBoot也是在当前微服务时代下流行的框架&#xff0c;并且…

SpringBoot自定义Starter(自动配置类)

前言 SpringBoot其实从诞生以来围绕的核心就是快速构建项目&#xff0c;快速构建的前提是有人帮你做好轮子&#xff0c;开发者只要拿来即用就好了&#xff0c;而造好轮子的人就是SpringBoot的开发者&#xff0c;引入自动配置的形式帮助开发者快速创建项目&#xff0c;而自动配…

Java并发编程之synchronized关键字解析

前言 公司加班太狠了&#xff0c;都没啥时间充电&#xff0c;这周终于结束了。这次整理了Java并发编程里面的synchronized关键字&#xff0c;又称为隐式锁&#xff0c;与JUC包中的Lock显示锁相对应&#xff1b;这个关键字从Java诞生开始就有&#xff0c;称之为重量级锁&#xf…

通过代理模式 + 责任链模式实现对目标执行方法拦截和增强功能

前言 最近需要实现一个插件功能&#xff0c;但是如果做成两个接口的话&#xff08;即执行前和执行后&#xff09;&#xff0c;那么会降低插件的可玩性&#xff0c;所以需做成类似AOP的环绕通知形式&#xff0c;所以就使用到了责任链模式和代理模式进行实现。 介绍 代理模式(P…

vscode Go 1.11.4 编译错误 need Delve built by Go 1.11 or later

更新golang的版本为1.11.4之后vscode编译错误&#xff1a;executables built by Go 1.11 or later need Delve built by Go 1.11 or later 原因是delve的版本太老了&#xff0c;需要更新&#xff0c;且delve的github地址已经更换&#xff0c;很多教程里的地址是不对的 新地址安…

Navicat使用教程:使用Navicat Query Analyzer优化查询性能(第1部分)

下载Navicat Monitor最新版本Navicat Monitor 是一套安全、简单而且无代理的远程服务器监控工具。它具有强大的功能使你的监控发挥最大效用。受监控的服务器包括 MySQL、MariaDB 和 Percona Server&#xff0c;并与 Amazon RDS、Amazon Aurora、Oracle Cloud、Microsoft Azure …

第一家云创大数据产业学院在佛山职业技术学院挂牌

2019年1月10日&#xff0c;“云创大数据产业学院揭牌暨战略合作协议签署仪式”在佛山职业技术学院电子信息学院会议室举行。云创大数据总裁刘鹏教授、市场部经理单明月&#xff0c;佛山职业技术学院电子信息学院院长唐建生、副院长田钧、学院办公室主任赵雪章、信息工程系主任乔…

String与StringBuffer和StringBuilder的根本区别

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

前端Http协议缓存初解

[TOC] 简介 用户获取网络资源&#xff0c;需要通过非常长的网络去服务器上请求资源,另外服务端为了应对大量的用户请求而不断的提升硬件性能与带宽。这对用户与服务端都非常的不友好。而缓存就是为了解决用户请求速度与释放服务器压力而生的。 为什么我会写Http缓存&#xff0c…

详解java访问修饰符

*************************************优雅的分割线 ********************************** 分享一波:程序员赚外快-必看的巅峰干货 如果以上内容对你觉得有用,并想获取更多的赚钱方式和免费的技术教程 请关注微信公众号:HB荷包 一个能让你学习技术和赚钱方法的公众号,持续更…

企业为什么要做SEO,它的重要性有哪些?

对于SEO工作而言&#xff0c;我们知道一个网站做SEO的基础诉求就是让用户和搜索引擎更好的理解网站内容&#xff0c;虽然随着搜索引擎算法技术的迭代&#xff0c;目前SEO面临更大的挑战与竞争&#xff0c;但基于搜索营销&#xff0c;它目前仍然显得十分重要。 那么&#xff0…

白话说编程之java线程

白话说编程之java线程线程和进程&#xff1a;进程&#xff1a;线程&#xff1a;线程和进程的区别&#xff1a;详解多线程:并发为什么使用并发并发的执行原理并行线程的五种状态&#xff1a;创建状态&#xff1a;就绪状态&#xff1a;运行状态&#xff1a;阻塞状态&#xff1a;死…

powerdesigner显示工具面板_photoshop教程-画笔工具预设与选项设置

定义画笔预设在打开的“画笔”面板中&#xff0c;单击左侧的“画笔笔尖形状”名称&#xff0c;可显示笔尖形状图案。单击“画笔”面板左侧其他不同的选项名称&#xff0c;在右侧就会显示其对应的调节项。只单击不同选项前面的方框&#xff0c;可使此选项有效&#xff0c;但右侧…

深入理解== 和 equals 的区别

深入理解 和 equals 的本质区别简介区别&#xff1a;图解&#xff1a;注意点&#xff1a;源码分析&#xff1a;总结分享一波:程序员赚外快-必看的巅峰干货简介 初学者常常被" “和‘equals ’所折磨&#xff0c;为什么&#xff0c;因为他们的大概意思相同&#xff0c;都是…

java sleep和wait区别

为什么80%的码农都做不了架构师&#xff1f;>>> 关于sleep和wait区别解析&#xff1a; sleep只是释放CPU资源&#xff0c;并不释放资源锁对象&#xff0c;wait是会释放掉资源锁对象。 比如&#xff0c;有个锁对象object&#xff0c;线程1和线程2都会锁住object对象…