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…

织梦缩略图自动补齐绝对路径_织梦生成文章内容缩略图时自动加上域名绝对路径...

今天又接了个织梦CMS的有偿服务,客户想要后台添加文章内容的时候,缩略图自动变成带上绝对路径的格式.比如我们默认的缩略图是这样的 /uploads/allimg/150814/123P2NB-0-lp.png 他想要的效果是这样的 http://www.youwujun.com.cn/uploads/allimg/150814/123P2NB-0-lp.png大家懂我…

BUAA 436 孟竹的复习计划(二维树状数组)

题目链接&#xff1a;http://acm.buaa.edu.cn/problem/436/ 题意&#xff1a;一个数列两种操作&#xff1a;&#xff08;1&#xff09;将某个位置的数字改成另一个数字&#xff1b;&#xff08;2&#xff09;交换两个位置的数字。每次操作之后输出逆序数的个数。 思路&#xff…

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

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

idea 导入svn代码_idea导入svn项目

起初和导入git项目一样&#xff0c;file - new - project from version control - &#xff0c;这后面选 subversion。在打开的 checkout from subversion对话框中&#xff0c;输入svn地址&#xff0c;比如 svn://11.22.33.44/demo。添加一个后&#xff0c;展开新加项&#xff…

由mysql8降级到mysql5

最近在研究liferay的使用。liferay可以连接mysql数据库。电脑中装的mysql的最新版本是mysql8。于是开始按照liferay的要求进行连接。但是多番尝试后&#xff0c;均报错&#xff1a;java.sql.SQLException: java.lang.ClassCastException: java.math.BigInteger cannot be cast …

tf计算矩阵维度_tensorflow中关于 多维tensor的运算(tf.multiply, tf.matmul, tf.tensordot)...

multiply 等同与* &#xff0c;用于计算矩阵之间的element-wise 乘法&#xff0c;要求矩阵的形状必须一致(或者是其中一个维度为1)&#xff0c;否则会报错&#xff1a;import tensorflow as tfa tf.constant([1, 2, 3, 4, 5, 6, 7, 8, 9, 10,11,12], shape[2, 3, 2])b tf.con…

Maven高级之插件开发

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

HTTP1.1新增了五种请求方法:OPTIONS、PUT、PATCH、DELETE、TRACE 、 CONNECT

200 &#xff08;成功&#xff09; 服务器已成功处理了请求。 通常&#xff0c;这表示服务器提供了请求的网页。 201 &#xff08;已创建&#xff09; 请求成功并且服务器创建了新的资源。 202 &#xff08;已接受&#xff09; 服务器已接受请求&#xff0c;但尚未处…

katalon进行app测试_Katalon API 测试 Demo

为何选择Katalon符合我们当下的情况&#xff0c;测试需要借助现有工具提高测试效率以及提高测试质量&#xff1b;为何不自己写代码&#xff1f;不是只有自己写的框架才是最好的&#xff0c;合适的才是最好的&#xff1b;katalon 支持ui、mobile、api 同时也支持脚本模式&#x…

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

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

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

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

cesium面积计算_cesium-长度测量和面积测量

(更新)多谢网友的提醒&#xff0c;面积测量的小问题已经修改&#xff0c;经测试可正常使用网上找的大神的实现方法有点问题&#xff0c;实现有一些bug&#xff0c;作为cesium新手一个&#xff0c;弃之不忍&#xff0c;只好硬着头皮修改了&#xff0c;不过还好问题不大&#xff…

SpringBoot自动配置原理流程

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

算法:对象方式数组去重

var arr [3, 1, 1, 4 , 2 , 4 , 2 , 4 , 2, 1, 1, 3, 3, 3];var ary[];var obj{};for(var i0;i<arr.length;i){var curarr[i];if(!obj[cur]){obj[cur]cur;ary.push(cur);}}console.log(ary); 复制代码

python实现路由功能_python 实现重启路由器

有一些服务&#xff0c;需要动态IP&#xff0c;所以我们用重启路由器的方法实现。人工重启不可选&#xff0c;用定时脚本执行即可。贴代码&#xff0c;每种路由器&#xff0c;提示不一样。需要路由器有telnet功能才行。#!/usr/bin/env python# -*- coding: utf-8 -*-import tel…

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

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

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

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

raidrive安装失败_记一次RaiDrive映射OneDrive遇到的问题

大概在1周以前&#xff0c;出于需要存放直播录像的原因&#xff0c;根据别人的视频教程去自己动手搞了个5T网盘的帐号。(体验一下&#xff0c;其实我还同时存一份在百度云&#xff0c;怕不稳定)用RaiDrive创建OneDrive的映射&#xff0c;在这步骤点确定后&#xff0c;会弹出微软…

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

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