Java ThreadLocal是什么

文章目录

  • 引子:SimpleDateFormat类
  • ThreadLocal是什么
  • ThreadLocal 的另一个用途
  • **总结**ThreadLocal的两大用途
  • ThreadLocal 的源代码
  • ThreadLocalMap
  • ThreadLocalMap 的问题
  • ThreadLocal的key为什么设置成弱引用?value为什么不是弱引用?
  • Thread、ThreadLocal、ThreadLocalMap 的关系
  • ThreadLocal带来的性能提升
  • ThreadLocalRandom类
  • 参考

引子:SimpleDateFormat类

为什么SimpleDateFormat 是线程不安全的?

  • 这主要是因为,它内部使用了一个全局的 Calendar 变量,来存储 date 信息

解决方式:

  1. 在sdf.parse()前后加锁,这也是我们一般的处理思路。
  2. 手动改造代码,给每个线程都new一个SimpleDateFormat
  3. 使用JDK的ThreadLocal类(和2的思想相似)

2代码如下

public class ThreadSafeSDFUsingMap {private Map<Long, SimpleDateFormat> sdfMap = new ConcurrentHashMap();//key 是线程 ID,value 是 SimpleDateFormatpublic String formatIt(Date date) {Thread currentThread = Thread.currentThread();long threadId = currentThread.getId();SimpleDateFormat sdf = sdfMap.get(threadId);if (null == sdf) {sdf = new SimpleDateFormat("yyyyMMdd HHmm");sdfMap.put(threadId, sdf);}return sdf.format(date);}
}

3代码如下

static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();public static class ParseDate implements Runnable {int i = 0;public ParseDate(int i) {this.i = i;}public void run() {try {if (tl.get() == null) {tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));}Date t = tl.get().parse("2015-03-29 19:29:" + i % 60);System.out.println(i + ":" + t);} catch (ParseException e) {e.printStackTrace();}}
}

从这里也可以看到,为每一个线程人手分配一个对象的工作并不是由ThreadLocal来完成的,而是需要在应用层面保证的。如果在应用上为每一个线程分配了相同的对象实例,那么ThreadLocal也不能保证线程安全。这点也需要大家注意。

ThreadLocal是什么

  • 用于创建线程的本地变量。可以使用get()\set()方法去获取他们的默认值或者在线程内部改变他们的值
  • 简单说ThreadLocal就是一种以空间换时间的做法,在每个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,自然就没有线程安全方面的问题了

ThreadLocal 的另一个用途

  • ThreadLocal 还有另一个用途,那就是保存线程上下文信息。这一点在很多框架乃至JDK类加载中都有用到。
  • 比如Spring的事务管理,方法A里头调用了方法B,方法B如果失败了,需要执行connection.rollback()来回滚事务。那么方法B怎么知道connection是哪个?最简单的就是方法A在调用方法B时,把connection对象传进去
  • 显然,这样很挫,需要修改方法的定义
  • 不过你现在知道ThreadLocal了,只需把connection塞入threadLocal,methodB和methodA在一个线程中执行,那么自然,methodB可以获取到和methodA相同的connection。
  • Spring 采用 Threadlocal 的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接,具体可以参考Spring的TransactionSynchronizationManager类

总结ThreadLocal的两大用途

  1. 实现线程安全;
  2. 保存线程上下文信息;

当然ThreadLocal肯定还有更多的用途,只要我们弄懂了它的原理,就知道如何灵活使用。

ThreadLocal 的源代码

设置到ThreadLocal中的数据,写入了threadLocals这个Map。其中,key为ThreadLocal当前对象,value就是我们需要的值。而threadLocals本身就保存了当前自己所在线程的所有“局部变量”,也就是一个ThreadLocal变量的集合

在set时,首先获得当前线程对象,然后通过getMap()拿到线程的ThreadLocalMap,并将值设入ThreadLocalMap中

get()方法先取得当前线程的ThreadLocalMap对象。然后,通过将自己作为key取得内部的实际数据

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)return (T)e.value;}return setInitialValue();
}ThreadLocalMap getMap(Thread t) {return t.threadLocals;
}

ThreadLocalMap

  • ThreadLocalMap 是 ThreadLocal 的内部类,没有实现 Map 接口,用独立的方式实现了 Map 的功能,其内部的 Entry 也独立实现。
  • 在 ThreadLocalMap 中,也是用 Entry 来保存 K-V 结构数据的。但是 Entry 中 key 只能是 ThreadLocal 对象 ,这点被 Entry 的构造方法已经限定死了。
  • 和 HashMap 的最大的不同在于,ThreadLocalMap 结构非常简单,没有 next 引用,也就是说 ThreadLocalMap 中解决 Hash 冲突的方式并非链表的方式,而是采用线性探测的方式:根据初始 key 的 hashcode 值确定元素在数组中的位置,如果发现这个位置上已经有其他 key 值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。
  • 显然 ThreadLocalMap 采用线性探测的方式解决 Hash 冲突的效率很低,如果有大量不同的 ThreadLocal 对象放入 map 中时发生冲突,或者发生二次冲突,则效率很低。
  • 所以这里引出的良好建议是:每个线程只存一个变量,这样的话所有的线程存放到 map 中的 Key 都是相同的 ThreadLocal,如果一个线程要保存多个变量,就需要创建多个 ThreadLocal,多个 ThreadLocal 放入 Map 中时会极大的增加 Hash 冲突的可能。

ThreadLocalMap 的问题

  • 由于 ThreadLocalMap 的 key 是引用,而 Value 是强引用。
  • 这就导致了一个问题,ThreadLocal 在没有外部对象强引用时,发生 GC 时弱引用 Key 会被回收,而 Value 不会回收,如果创建 ThreadLocal 的线程一直持续运行,那么这个 Entry 对象中的 value 就有可能一直得不到回收,发生内存泄露

如何避免泄漏

  • 既然 Value 是强引用,那么我们要做的事,就是在调用 ThreadLocal 的 get()、set() 方法时完成后再调用 remove 方法,将 Entry 节点和 Map 的引用关系移除,这样整个 Entry 对象在 GC Roots 分析后就变成不可达了,下次 GC 的时候就可以被回收。
  • 如果使用 ThreadLocal 的 set 方法之后,没有显示的调用 remove 方法,就有可能发生内存泄露,所以养成良好的编程习惯十分重要,使用完 ThreadLocal 之后,记得调用 remove 方法

ThreadLocal的key为什么设置成弱引用?value为什么不是弱引用?

假如key对ThreadLocal对象的弱引用,改为强引用。

即使ThreadLocal变量生命周期完了,设置成null了,但由于Entry中的key对ThreadLocal还是强引用。

就会存在这样的强引用链:Thread变量 -> Thread对象 -> ThreadLocalMap -> Entry -> key -> ThreadLocal对象。

假设该线程一直存在,那么,ThreadLocal对象和ThreadLocalMap都将不会被GC回收,于是产生了内存泄露问题。

但如果是弱引用,当ThreadLocal变量=null之后(强引用没了),只剩下key的弱引用,gc就会自动回收ThreadLocal对象

所以这里要理解的是:如果强引用和弱引用同时引用一个对象,那么这个对象不会被GC回收


Entry的value一般只被Entry引用,有可能没被业务系统中的其他地方引用。如果将value改成了弱引用,被GC贸然回收了(数据突然没了),可能会导致业务系统出现异常。

参考:threadlocal value为什么不是弱引用?

Thread、ThreadLocal、ThreadLocalMap 的关系

在这里插入图片描述

ThreadLocal带来的性能提升

多线程下产生随机数的性能对比(多线程共用同一个Random类、使用ThreadLocal实现每个线程各自持有一个Random类)

注意Random类是线程安全的

package com.space.java.juc.threadlocal;import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;public class RandomDemo {public static final int GEN_COUNT = 10000000;//生成随机数的数量public static final int THREAD_COUNT = 4;//线程数量static ExecutorService exe = Executors.newFixedThreadPool(THREAD_COUNT);public static Random rnd = new Random(123);public static ThreadLocal<Random> tRnd = new ThreadLocal<Random>() {// 当ThreadLocal get为null时,会调用此方法初始化@Overrideprotected Random initialValue() {return new Random(123);}};public static class RndTask implements Callable<Long> {private int mode = 0;public RndTask(int mode) {this.mode = mode;}public Random getRandom() {if (mode == 0) {return rnd;} else if (mode == 1) {return tRnd.get();} else {return null;}}@Overridepublic Long call() {long b = System.currentTimeMillis();for (long i = 0; i < GEN_COUNT; i++) {getRandom().nextInt();}long e = System.currentTimeMillis();System.out.println(Thread.currentThread().getName() + " spend " + (e - b) + "ms");return e - b;}}public static void main(String[] args) throws InterruptedException, ExecutionException {Future<Long>[] futs = new Future[THREAD_COUNT];for (int i = 0; i < THREAD_COUNT; i++) {futs[i] = exe.submit(new RndTask(0));}long totaltime = 0;for (int i = 0; i < THREAD_COUNT; i++) {totaltime += futs[i].get();}System.out.println("多线程访问同一个Random实例:" + totaltime + "ms");// ThreadLocal的情况for (int i = 0; i < THREAD_COUNT; i++) {futs[i] = exe.submit(new RndTask(1));}totaltime = 0;for (int i = 0; i < THREAD_COUNT; i++) {totaltime += futs[i].get();}System.out.println("使用ThreadLocal包装Random实例:" + totaltime + "ms");exe.shutdown();}
}

ThreadLocalRandom类

Random是线程安全的,但由于内部使用cas机制,导致它不是高并发的

而ThreadLocalRandom是一个性能强悍的高并发随机数生成器,ThreadLocalRandom继承自Random

和ThreadLocal的原理类似,Thread类内部有一些变量专门用于让随机数生成器只访问本地线程数据,从而避免竞争

基本使用方法:

public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Player().start();}
}private static class Player extends Thread {@Overridepublic void run() {System.out.println(getName() + ": " + ThreadLocalRandom.current().nextInt(100));}
}

用于生成随机订单编号

private static final SimpleDateFormat dateFormatOne=new SimpleDateFormat("yyyyMMddHHmmssSS");private static final ThreadLocalRandom random=ThreadLocalRandom.current();/*** 生成订单编号-方式一* @return*/
public static String generateOrderCode(){//时间戳+N为随机数流水号return dateFormatOne.format(DateTime.now().toDate()) + generateNumber(4);
}//N为随机数流水号
public static String generateNumber(final int num){StringBuffer sb=new StringBuffer();for (int i=1;i<=num;i++){sb.append(random.nextInt(9));}return sb.toString();
}

参考

  • 《实战Java高并发程序设计》4.3节
  • 拼多多面试官没想到ThreadLocal我用得这么溜,人直接傻掉
  • threadlocal value为什么不是弱引用?
  • 高并发情况下你还在用Random生成随机数?

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

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

相关文章

如何让你的视频在 TikTok上变得火爆?

TikTok凭借巨大的用户量和商业价值&#xff0c;它从来不缺优质内容。如何在众多内容中脱颖而出获得关注&#xff0c;这并不简单。和泛流量账号不同&#xff0c;商业账号的目的更加明确&#xff0c;也就是说&#xff0c;商业账号并不一定要以高流量最为唯一的追求目标&#xff0…

跨域+四种解决方法

文章目录 一、跨域二、JSONP实现跨域请求三、前端代理实现跨域请求四、后端设置请求头实现跨域请求五、Nginx代理实现跨域请求5.1 安装Nginx软件5.2 使用Ubuntu安装nginx 本文是在学习课程满神yyds后记录的笔记&#xff0c;强烈推荐读者去看此课程。 一、跨域 出于浏览器的同…

JVM笔记 —— 出现内存溢出错误时时如何排查

一、出现内存溢出的几种情况 内存溢出错误分为StackOverflowError和OutOfMemoryError&#xff0c;前者是栈中出现溢出&#xff0c;后者一般是堆或方法区出现溢出&#xff0c;简称OOM 1. 栈溢出 StackOverflowError 栈溢出一般都是因为没有正确的结束递归导致的&#xff0c;无…

Qt5兼容使用之前Qt4接口 intersect接口

1. 问题 项目卡中遇到编译报错&#xff0c; 错误 C2039 “intersect”: 不是“QRect”的成员 。 2. 排查过程 排查到依赖的第三方代码&#xff0c;使用 intersect 接口&#xff0c; 跟踪排查到头文件中使用了***#if QT_DEPRECATED_SINCE(5, 0)*** #if QT_DEPRECATED_SINCE…

【CSS】CSS 选择器

CSS 选择器 1.基础选择器 1.1 元素选择器 语法&#xff1a;标签名{...} 元素选择器会选中对应标签名的HTML元素&#xff0c;例如&#xff1a;p{...}&#xff0c;div{...}&#xff0c;span{...}等 1.2 类选择器 语法&#xff1a;.类名{...} 类选择器会选中class属性为指定…

[Idea热部署]两秒钟学会热部署

两者同时适配好,保证没有问题 哈&#xff0c;谢谢各位同志的阅读&#xff0c;然后呢如果觉得本文对您有所帮助的话&#xff0c;还给个免费的赞捏 Thanks♪(&#xff65;ω&#xff65;)&#xff89;

【ArcGIS Pro二次开发】(58):数据的本地化存储

在做村规工具的过程中&#xff0c;需要设置一些参数&#xff0c;比如说导图的DPI&#xff0c;需要导出的图名等等。 每次导图前都需要设置参数&#xff0c;虽然有默认值&#xff0c;但还是需要不时的修改。 在使用的过程中&#xff0c;可能会有一些常用的参数&#xff0c;希望…

每天一道leetcode:139. 单词拆分(动态规划中等)

今日份题目&#xff1a; 给你一个字符串 s 和一个字符串列表 wordDict 作为字典。请你判断是否可以利用字典中出现的单词拼接出 s 。 注意&#xff1a;不要求字典中出现的单词全部都使用&#xff0c;并且字典中的单词可以重复使用。 示例1 输入: s "leetcode", …

【解密算法:时间与空间的博弈】

本章重点 ​​什么是数据结构&#xff1f; 什么是算法&#xff1f; 算法效率 时间复杂度 空间复杂度 常见时间复杂度以及复杂度oj练习 1. 什么是数据结构&#xff1f; 数据结构(Data Structure)是计算机存储、组织数据的方式&#xff0c;指相互之间存在一种或多种特定关系…

Docker 数据管理

文章目录 前言1、Dcoker 文件体系2、volume挂载案例2.1、挂载运行一个容器实例方法1方法2 3、volumes-from 案例4、备份/恢复数据卷5、删除数据卷 前言 为什么要有数据管理&#xff1f; 因为&#xff1a; Docker 是不提供持久化的 &#xff0c;容器是不稳定的&#xff1b;一个…

TCP 三次握手,四次挥手

1、三次握手 第一次握手 SYN 等于1&#xff0c;SeqX 第二次握手 SYN等于1 ACK等于1&#xff0c;SeqY&#xff0c;AckX1 第三次SYN等于0 ACK等于1&#xff0c;SeqX1&#xff0c;AckY1 ackRow都是对应请求seqraw&#xff0c;三次握手后&#xff0c;Seq就是服务器前一个包中的ac…

【Git】—— 标签管理

目录 &#xff08;一&#xff09;理解标签 1、作用 &#xff08;二&#xff09;创建标签 &#xff08;三&#xff09;操作标签 1、删除标签 2、推送标签 3、删除远程标签 &#xff08;一&#xff09;理解标签 标签 tag &#xff0c;可以简单的理解为是对某次 commit 的…

Java基础篇--运算符

目录 算术运算符 赋值运算符 比较运算符 逻辑运算符 条件运算符&#xff08;?:&#xff09; instanceof 运算符 Java运算符优先级 在程序中经常出现一些特殊符号&#xff0c;如、-、*、、>等&#xff0c;这些特殊符号称作运算符。运算符用于对数据进行算术运算、赋值…

flowable-ui部署(6.80)

不使用tomcat直接看最后边 前置条件&#xff1a;Apache Tomcat/9.0.78版本及以下 https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.78/bin/apache-tomcat-9.0.78-windows-x64.zip 一、下载资源 https://github.com/flowable/flowable-engine/releases/download/flowable-6.…

vue3+ts使用antv/x6

使用 2.x 版本 x6.antv 新官网: 安装 npm install antv/x6 //"antv/x6": "^2.1.6",项目结构 1、初始化画布 index.vue <template><div id"container"></div> </template><script setup langts> import { onM…

TCP网络服务器设计

最近设计了一个网络服务器程序&#xff0c;对于4C8G的机器配置&#xff0c;TPS可以达到5W。业务处理逻辑是简单的字符串处理。服务器接收请求后对下游进行类似广播的发送。在此分享一下设计方式&#xff0c;如果有改进思路欢迎大家交流分享。 程序运行在CentOS7.9操作系统上&a…

yum 安装本地包 rpm

有时直接yum install 有几个包死活下不下来 根据网址&#xff0c;手动下载&#xff0c;下载后上传至 centos 然后运行 sudo yum localinstall xxx.rpm 即可安装 参考 https://blog.csdn.net/weiguang1017/article/details/52293244

利用状态监测和机器学习提高冷却塔性能的具体方法

在现代工业生产中&#xff0c;冷却塔扮演着至关重要的角色&#xff0c;它们的性能直接影响着工艺流程的稳定性和效率。为了确保冷却塔的正常运行和减少系统故障&#xff0c;状态监测和机器学习成为了关键技术。 图.冷却塔&#xff08;PreMaint&#xff09; 在前文《基于人工智…

开发工具IDEA的下载与初步使用【各种快捷键的设置,使你的开发事半功倍】

&#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于IDEA的相关操作吧 目录 &#x1f973;&#x1f973;Welcome Huihuis Code World ! !&#x1f973;&#x1f973; 一.IDEA的简介以及优势 二.IDEA的下载 1.下…

Android应用开发(35)SufaceView基本用法

Android应用开发学习笔记——目录索引 参考Android官网&#xff1a;https://developer.android.com/reference/android/view/SurfaceView 一、SurfaceView简介 SurfaceView派生自View&#xff0c;提供嵌入视图层次结构内部的专用绘图表面&#xff0c;SurfaceView可以在主线程之…