learn.lianglianglee.com/文章/Java中的ThreadLocal.md.html
2022-05-11 18:52:13 +08:00

1885 lines
39 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>Java中的ThreadLocal.md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/文章/AQS 万字图文全面解析.md">AQS 万字图文全面解析.md.html</a>
</li>
<li>
<a href="/文章/Docker 镜像构建原理及源码分析.md">Docker 镜像构建原理及源码分析.md.html</a>
</li>
<li>
<a href="/文章/ElasticSearch 小白从入门到精通.md">ElasticSearch 小白从入门到精通.md.html</a>
</li>
<li>
<a href="/文章/JVM CPU Profiler技术原理及源码深度解析.md">JVM CPU Profiler技术原理及源码深度解析.md.html</a>
</li>
<li>
<a href="/文章/JVM 垃圾收集器.md">JVM 垃圾收集器.md.html</a>
</li>
<li>
<a href="/文章/JVM 面试的 30 个知识点.md">JVM 面试的 30 个知识点.md.html</a>
</li>
<li>
<a href="/文章/Java IO 体系、线程模型大总结.md">Java IO 体系、线程模型大总结.md.html</a>
</li>
<li>
<a href="/文章/Java NIO浅析.md">Java NIO浅析.md.html</a>
</li>
<li>
<a href="/文章/Java 面试题集锦(网络篇).md">Java 面试题集锦(网络篇).md.html</a>
</li>
<li>
<a href="/文章/Java-直接内存 DirectMemory 详解.md">Java-直接内存 DirectMemory 详解.md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md">Java中9种常见的CMS GC问题分析与解决.md.html</a>
</li>
<li>
<a href="/文章/Java中9种常见的CMS GC问题分析与解决.md">Java中9种常见的CMS GC问题分析与解决.md.html</a>
</li>
<li>
<a href="/文章/Java中的SPI.md">Java中的SPI.md.html</a>
</li>
<li>
<a class="current-tab" href="/文章/Java中的ThreadLocal.md">Java中的ThreadLocal.md.html</a>
</li>
<li>
<a href="/文章/Java线程池实现原理及其在美团业务中的实践.md">Java线程池实现原理及其在美团业务中的实践.md.html</a>
</li>
<li>
<a href="/文章/Java魔法类Unsafe应用解析.md">Java魔法类Unsafe应用解析.md.html</a>
</li>
<li>
<a href="/文章/Kafka 源码阅读笔记.md">Kafka 源码阅读笔记.md.html</a>
</li>
<li>
<a href="/文章/Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md">Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用原理.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB Buffer Pool.md">MySQL · 引擎特性 · InnoDB Buffer Pool.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB IO子系统.md">MySQL · 引擎特性 · InnoDB IO子系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 事务系统.md">MySQL · 引擎特性 · InnoDB 事务系统.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 同步机制.md">MySQL · 引擎特性 · InnoDB 同步机制.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB 数据页解析.md">MySQL · 引擎特性 · InnoDB 数据页解析.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · InnoDB崩溃恢复.md">MySQL · 引擎特性 · InnoDB崩溃恢复.md.html</a>
</li>
<li>
<a href="/文章/MySQL · 引擎特性 · 临时表那些事儿.md">MySQL · 引擎特性 · 临时表那些事儿.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 半同步复制.md">MySQL 主从复制 半同步复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制 基于GTID复制.md">MySQL 主从复制 基于GTID复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 主从复制.md">MySQL 主从复制.md.html</a>
</li>
<li>
<a href="/文章/MySQL 事务日志(redo log和undo log).md">MySQL 事务日志(redo log和undo log).md.html</a>
</li>
<li>
<a href="/文章/MySQL 亿级别数据迁移实战代码分享.md">MySQL 亿级别数据迁移实战代码分享.md.html</a>
</li>
<li>
<a href="/文章/MySQL 从一条数据说起-InnoDB行存储数据结构.md">MySQL 从一条数据说起-InnoDB行存储数据结构.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:事务和锁的面纱.md">MySQL 地基基础:事务和锁的面纱.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据字典.md">MySQL 地基基础:数据字典.md.html</a>
</li>
<li>
<a href="/文章/MySQL 地基基础:数据库字符集.md">MySQL 地基基础:数据库字符集.md.html</a>
</li>
<li>
<a href="/文章/MySQL 性能优化:碎片整理.md">MySQL 性能优化:碎片整理.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md">MySQL 故障诊断:一个 ALTER TALBE 执行了很久,你慌不慌?.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:如何在日志中轻松定位大事务.md">MySQL 故障诊断:如何在日志中轻松定位大事务.md.html</a>
</li>
<li>
<a href="/文章/MySQL 故障诊断:教你快速定位加锁的 SQL.md">MySQL 故障诊断:教你快速定位加锁的 SQL.md.html</a>
</li>
<li>
<a href="/文章/MySQL 日志详解.md">MySQL 日志详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL 的半同步是什么?.md">MySQL 的半同步是什么?.md.html</a>
</li>
<li>
<a href="/文章/MySQL中的事务和MVCC.md">MySQL中的事务和MVCC.md.html</a>
</li>
<li>
<a href="/文章/MySQL事务_事务隔离级别详解.md">MySQL事务_事务隔离级别详解.md.html</a>
</li>
<li>
<a href="/文章/MySQL优化优化 select count().md">MySQL优化优化 select count().md.html</a>
</li>
<li>
<a href="/文章/MySQL共享锁、排他锁、悲观锁、乐观锁.md">MySQL共享锁、排他锁、悲观锁、乐观锁.md.html</a>
</li>
<li>
<a href="/文章/MySQL的MVCC多版本并发控制.md">MySQL的MVCC多版本并发控制.md.html</a>
</li>
<li>
<a href="/文章/QingStor 对象存储架构设计及最佳实践.md">QingStor 对象存储架构设计及最佳实践.md.html</a>
</li>
<li>
<a href="/文章/RocketMQ 面试题集锦.md">RocketMQ 面试题集锦.md.html</a>
</li>
<li>
<a href="/文章/SnowFlake 雪花算法生成分布式 ID.md">SnowFlake 雪花算法生成分布式 ID.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md">Spring Boot 2.x 结合 k8s 实现分布式微服务架构.md.html</a>
</li>
<li>
<a href="/文章/Spring Boot 教程:如何开发一个 starter.md">Spring Boot 教程:如何开发一个 starter.md.html</a>
</li>
<li>
<a href="/文章/Spring MVC 原理.md">Spring MVC 原理.md.html</a>
</li>
<li>
<a href="/文章/Spring MyBatis和Spring整合的奥秘.md">Spring MyBatis和Spring整合的奥秘.md.html</a>
</li>
<li>
<a href="/文章/Spring 帮助你更好的理解Spring循环依赖.md">Spring 帮助你更好的理解Spring循环依赖.md.html</a>
</li>
<li>
<a href="/文章/Spring 循环依赖及解决方式.md">Spring 循环依赖及解决方式.md.html</a>
</li>
<li>
<a href="/文章/Spring中眼花缭乱的BeanDefinition.md">Spring中眼花缭乱的BeanDefinition.md.html</a>
</li>
<li>
<a href="/文章/Vert.x 基础入门.md">Vert.x 基础入门.md.html</a>
</li>
<li>
<a href="/文章/eBay 的 Elasticsearch 性能调优实践.md">eBay 的 Elasticsearch 性能调优实践.md.html</a>
</li>
<li>
<a href="/文章/不可不说的Java“锁”事.md">不可不说的Java“锁”事.md.html</a>
</li>
<li>
<a href="/文章/互联网并发限流实战.md">互联网并发限流实战.md.html</a>
</li>
<li>
<a href="/文章/从ReentrantLock的实现看AQS的原理及应用.md">从ReentrantLock的实现看AQS的原理及应用.md.html</a>
</li>
<li>
<a href="/文章/从SpringCloud开始聊微服务架构.md">从SpringCloud开始聊微服务架构.md.html</a>
</li>
<li>
<a href="/文章/全面了解 JDK 线程池实现原理.md">全面了解 JDK 线程池实现原理.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性理论与算法.md">分布式一致性理论与算法.md.html</a>
</li>
<li>
<a href="/文章/分布式一致性算法 Raft.md">分布式一致性算法 Raft.md.html</a>
</li>
<li>
<a href="/文章/分布式唯一 ID 解析.md">分布式唯一 ID 解析.md.html</a>
</li>
<li>
<a href="/文章/分布式链路追踪:集群管理设计.md">分布式链路追踪:集群管理设计.md.html</a>
</li>
<li>
<a href="/文章/动态代理种类及原理,你知道多少?.md">动态代理种类及原理,你知道多少?.md.html</a>
</li>
<li>
<a href="/文章/响应式架构与 RxJava 在有赞零售的实践.md">响应式架构与 RxJava 在有赞零售的实践.md.html</a>
</li>
<li>
<a href="/文章/大数据算法——布隆过滤器.md">大数据算法——布隆过滤器.md.html</a>
</li>
<li>
<a href="/文章/如何优雅地记录操作日志?.md">如何优雅地记录操作日志?.md.html</a>
</li>
<li>
<a href="/文章/如何设计一个亿级消息量的 IM 系统.md">如何设计一个亿级消息量的 IM 系统.md.html</a>
</li>
<li>
<a href="/文章/异步网络模型.md">异步网络模型.md.html</a>
</li>
<li>
<a href="/文章/当我们在讨论CQRS时我们在讨论些神马.md">当我们在讨论CQRS时我们在讨论些神马.md.html</a>
</li>
<li>
<a href="/文章/彻底理解 MySQL 的索引机制.md">彻底理解 MySQL 的索引机制.md.html</a>
</li>
<li>
<a href="/文章/最全的 116 道 Redis 面试题解答.md">最全的 116 道 Redis 面试题解答.md.html</a>
</li>
<li>
<a href="/文章/有赞权限系统(SAM).md">有赞权限系统(SAM).md.html</a>
</li>
<li>
<a href="/文章/有赞零售中台建设方法的探索与实践.md">有赞零售中台建设方法的探索与实践.md.html</a>
</li>
<li>
<a href="/文章/服务注册与发现原理剖析Eureka、Zookeeper、Nacos.md">服务注册与发现原理剖析Eureka、Zookeeper、Nacos.md.html</a>
</li>
<li>
<a href="/文章/深入浅出Cache.md">深入浅出Cache.md.html</a>
</li>
<li>
<a href="/文章/深入理解 MySQL 底层实现.md">深入理解 MySQL 底层实现.md.html</a>
</li>
<li>
<a href="/文章/漫画讲解 git rebase VS git merge.md">漫画讲解 git rebase VS git merge.md.html</a>
</li>
<li>
<a href="/文章/生成浏览器唯一稳定 ID 的探索.md">生成浏览器唯一稳定 ID 的探索.md.html</a>
</li>
<li>
<a href="/文章/缓存 如何保证缓存与数据库的双写一致性?.md">缓存 如何保证缓存与数据库的双写一致性?.md.html</a>
</li>
<li>
<a href="/文章/网易严选怎么做全链路监控的?.md">网易严选怎么做全链路监控的?.md.html</a>
</li>
<li>
<a href="/文章/美团万亿级 KV 存储架构与实践.md">美团万亿级 KV 存储架构与实践.md.html</a>
</li>
<li>
<a href="/文章/美团点评Kubernetes集群管理实践.md">美团点评Kubernetes集群管理实践.md.html</a>
</li>
<li>
<a href="/文章/美团百亿规模API网关服务Shepherd的设计与实现.md">美团百亿规模API网关服务Shepherd的设计与实现.md.html</a>
</li>
<li>
<a href="/文章/解读《阿里巴巴 Java 开发手册》背后的思考.md">解读《阿里巴巴 Java 开发手册》背后的思考.md.html</a>
</li>
<li>
<a href="/文章/认识 MySQL 和 Redis 的数据一致性问题.md">认识 MySQL 和 Redis 的数据一致性问题.md.html</a>
</li>
<li>
<a href="/文章/进阶Dockerfile 高阶使用指南及镜像优化.md">进阶Dockerfile 高阶使用指南及镜像优化.md.html</a>
</li>
<li>
<a href="/文章/铁总在用的高性能分布式缓存计算框架 Geode.md">铁总在用的高性能分布式缓存计算框架 Geode.md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md">阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html</a>
</li>
<li>
<a href="/文章/阿里云PolarDB及其共享存储PolarFS技术实现分析.md">阿里云PolarDB及其共享存储PolarFS技术实现分析.md.html</a>
</li>
<li>
<a href="/文章/面试最常被问的 Java 后端题.md">面试最常被问的 Java 后端题.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计在互联网业务开发中的实践.md">领域驱动设计在互联网业务开发中的实践.md.html</a>
</li>
<li>
<a href="/文章/领域驱动设计的菱形对称架构.md">领域驱动设计的菱形对称架构.md.html</a>
</li>
<li>
<a href="/文章/高效构建 Docker 镜像的最佳实践.md">高效构建 Docker 镜像的最佳实践.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>Java中的ThreadLocal</h1>
<h2>前言</h2>
<p>面试的时候被问到ThreadLocal的相关知识没有回答好奶奶的现在感觉问啥都能被问倒所以我决定先解决这几次面试中都遇到的高频问题把这几个硬骨头都能理解的透彻的说出来了感觉最起码不能总是一轮游。</p>
<h3>ThreadLocal介绍</h3>
<p>ThreadLocal是JDK1.2开始就提供的一个用来存储线程本地变量的类。ThreadLocal中的变量是在每个线程中独立存在的当多个线程访问ThreadLocal中的变量的时候其实都是访问的自己当前线程的内存中的变量从而保证的变量的线程安全。</p>
<p>我们一般在使用ThreadLocal的时候都是为了解决线程中存在的变量竞争问题。其实解决这类问题通常大家也会想到使用synchronized来加锁解决。</p>
<p>例如在解决SimpleDateFormat的线程安全的时候。SimpleDateFormat是非线程安全的它里面无论的是format()方法还是parse()方法都有使用它自己内部的一个Calendar类的对象format方法是设置时间parse()方法里面是先调用Calendar的clear()方法然后又调用了Calendar的set()方法赋值如果一个线程刚调用了set()进行赋值这个时候又来了一个线程直接调用了clear()方法那么这个parse()方法执行的结果就会有问题的。
<strong>解决办法一</strong>
将使用SimpleDateformat的方法加上synchronized这样虽然保证了线程安全但却降低了效率同一时间只有一个线程能使用格式化时间的方法。</p>
<pre><code class="language-java">private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss&quot;);
public static synchronized String formatDate(Date date){
return simpleDateFormat.format(date);
}
</code></pre>
<p><strong>解决办法二</strong>
将SimpleDateFormat的对象放到ThreadLocal里面这样每个线程中都有一个自己的格式对象的副本了。互不干扰从而保证了线程安全。</p>
<pre><code class="language-java">private static final ThreadLocal&lt;SimpleDateFormat&gt; simpleDateFormatThreadLocal = ThreadLocal.withInitial(() -&gt; new SimpleDateFormat(&quot;yyyy-MM-dd HH:mm:ss&quot;));
public static String formatDate(Date date){
return simpleDateFormatThreadLocal.get().format(date);
}
</code></pre>
<h3>ThreadLocal的原理</h3>
<p>我们先看一下ThreadLocal是怎么使用的。</p>
<pre><code class="language-java">ThreadLocal&lt;Integer&gt; threadLocal99 = new ThreadLocal&lt;Integer&gt;();
threadLocal99.set(3);
int num = threadLocal99.get();
System.out.println(&quot;数字:&quot;+num);
threadLocal99.remove();
System.out.println(&quot;数字Empty:&quot;+threadLocal99.get());
</code></pre>
<p>运行结果:</p>
<pre><code class="language-java">数字:3
数字Empty:null
</code></pre>
<p>使用起来很简单主要是将变量放到ThreadLocal里面在线程执行过程中就可以取到当执行完成后在remove掉就可以了只要没有调用remove()当前线程在执行过程中都是可以拿到变量数据的。
因为是放到了当前执行的线程中所以ThreadLocal中的变量值只能当前线程来使用从而保证的了线程安全当前线程的子线程其实也是可以获取到的</p>
<p>来看一下ThreadLocal的set()方法源码</p>
<pre><code class="language-java">public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 获取ThreadLocalMap
ThreadLocal.ThreadLocalMap map = getMap(t);
// ThreadLocalMap 对象是否为空不为空则直接将数据放入到ThreadLocalMap中
if (map != null)
map.set(this, value);
else
createMap(t, value); // ThreadLocalMap对象为空则先创建对象再赋值。
}
</code></pre>
<p>我们看到变量都是存放在了ThreadLocalMap这个变量中的。那么ThreadLocalMap又是怎么来的呢</p>
<pre><code class="language-java">ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
public class Thread implements Runnable {
... ...
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
... ...
}
</code></pre>
<p>通过上面的源码我们发现ThreadLocalMap变量是当前执行线程中的一个变量所以说ThreadLocal中存放的数据其实都是放到了当前执行线程中的一个变量里面了。也就是存储在了当前的线程对象里了别的线程里面是另一个线程对象了拿不到其他线程对象中的数据所以数据自然就隔离开了。</p>
<p><strong>那么ThreadLocalMap是怎么存储数据的呢</strong>
ThreadLocalMap 是ThreadLocal类里的一个内部类虽然类的名字上带着Map但却没有实现Map接口只是结构和Map类似而已。
<img src="assets/20200909231451433.png" alt="在这里插入图片描述" />
ThreadLocalMap内部其实是一个Entry数组Entry是ThreadLocalMap中的一个内部类继承自WeakReference并将ThreadLocal类型的对象设置为了Entry的Key以及对Key设置成弱引用。
ThreadLocalMap的内部数据结构就大概是这样的key,value组成的Entry的数组集合。
<img src="assets/2020090923454535.png" alt="在这里插入图片描述" />
和真正的Map还是有区别的没有链表了这样在解决key的hash冲突的时候措施肯定就和HashMap不一样了。
一个线程中是可以创建多个ThreadLocal对象的多个ThreadLocal对象就会存放多个数据那么在ThreadLocalMap中就会以数组的形式存放这些数据。
我们来看一下具体的ThreadLocalMap的set()方法的源码</p>
<pre><code class="language-java">/**
* Set the value associated with key.
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal&lt;?&gt; 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 &amp; (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal&lt;?&gt; k = e.get();
// 如果当前位置不为空并且当前位置的key和传过来的key相等那么就会覆盖当前位置的数据
if (k == key) {
e.value = value;
return;
}
// 如果当前位置为空则初始化一个Entry对象放到当前位置。
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
// 如果当前位置不为空并且当前位置的key也不等于要赋值的key ,那么将去找下一个空位置,直接将数据放到下一个空位置处。
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) &amp;&amp; sz &gt;= threshold)
rehash();
}
</code></pre>
<p>我们从set()方法中可以看到,处理逻辑有四步。</p>
<ul>
<li>第一步先根据Threadlocal对象的hashcode和数组长度做与运算获取数据应该放在当前数组中的位置。</li>
<li>第二步就是判断当前位置是否为空为空的话就直接初始化一个Entry对象放到当前位置。</li>
<li>第三步如果当前位置不为空而当前位置的Entry中的key和传过来的key一样那么直接覆盖掉当前位置的数据。</li>
<li>第四步如果当前位置不为空并且当前位置的Entry中的key和传过来的key
也不一样,那么就会去找下一个空位置,然后将数据存放到空位置(<strong>数组超过长度后,会执行扩容的</strong></li>
</ul>
<p>在get的时候也是类似的逻辑先通过传入的ThreadLocal的hashcode获取在Entry数组中的位置然后拿当前位置的Entry的Key和传入的ThreadLocal对比相等的话直接把数据返回如果不相等就去判断和数组中的下一个值的key是否相等。。。</p>
<pre><code class="language-java">private Entry getEntry(ThreadLocal&lt;?&gt; key) {
int i = key.threadLocalHashCode &amp; (table.length - 1);
Entry e = table[i];
if (e != null &amp;&amp; e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
/**
* Version of getEntry method for use when key is not found in
* its direct hash slot.
*
* @param key the thread local object
* @param i the table index for key's hash code
* @param e the entry at table[i]
* @return the entry associated with key, or null if no such
*/
private Entry getEntryAfterMiss(ThreadLocal&lt;?&gt; key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;
while (e != null) {
ThreadLocal&lt;?&gt; k = e.get();
if (k == key)
return e;
if (k == null)
expungeStaleEntry(i);
else
i = nextIndex(i, len);
e = tab[i];
}
return null;
}
</code></pre>
<p>我们上文一直说ThreadLocal是保存在单个线程中的数据每个线程都有自己的数据但是实际ThreadLocal里面的真正的对象数据其实是保存在堆里面的而线程里面只是存储了对象的引用而已。
并且我们在使用的时候通常需要在上一个线程执行的方法的上下文共享ThreadLocal中的变量。
例如我的主线程是在某个方法中执行代码呢但是这个方法中有一段代码时新创建了一个线程在这个线程里面还使用了我这个正在执行的方法里面的定义的ThreadLocal里面的变量。这个时候就是需要从新线程里面调用外面线程的数据这个就需要线程间共享了。这种子父线程共享数据的情况ThreadLocal也是支持的。
例如:</p>
<pre><code class="language-java"> ThreadLocal threadLocalMain = new InheritableThreadLocal();
threadLocalMain.set(&quot;主线程变量&quot;);
Thread t = new Thread() {
@Override
public void run() {
super.run();
System.out.println( &quot;现在获取的变量是 =&quot; + threadLocalMain.get());
}
};
t.start();
</code></pre>
<p>运行结果:</p>
<pre><code class="language-java">现在获取的变量是 =主线程变量
</code></pre>
<p>上面这样的代码就能实现子父线程共享数据的情况重点是使用InheritableThreadLocal来实现的共享。
那么它是怎么实现数据共享的呢?
在Thread类的init()方法中有这么一段代码:</p>
<pre><code class="language-java">if (inheritThreadLocals &amp;&amp; parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
</code></pre>
<p>这段代码的意思是在创建线程的时候如果当前线程的inheritThreadLocals变量和父线程的inheritThreadLocals变量都不为空的时候会将父线程的inheritThreadLocals变量中的数据赋给当前线程中的inheritThreadLocals变量。</p>
<h3>ThreadLocal的内存泄漏问题</h3>
<p>上文我们也提到过ThreadLocal中的ThreadLocalMap里面的Entry对象是继承自WeakReference类的说明Entry的key是一个弱引用。</p>
<p><img src="assets/20200910083829900.png" alt="在这里插入图片描述" /></p>
<blockquote>
<p>弱引用是用来描述那些非必须的对象,弱引用的对象,只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。</p>
</blockquote>
<p><strong>这个弱引用还是ThreadLocal对象本身所以一般在线程执行完成后ThreadLocal对象就会变成null了而为null的弱引用对象在下一次GC的时候就会被清除掉这样Entry的Key的内存空间就被释放出来了但是Entry的value还在占用的内存如果线程是被复用的例如线程池中的线程后面也不使用ThreadLocal存取数据了那么这里面的value值会一直存在最终就导致了内存泄漏。</strong></p>
<p><strong>防止内存泄漏的办法就是在每次使用完ThreadLocal的时候都去执行以下remove()方法就可以把key和value的空间都释放了。</strong></p>
<h3>那既然容易产生内存泄漏,为什么还要设置成弱引用的呢?</h3>
<p>如果正常情况下应该是强引用但是强引用只要引用关系还在就一直不会被回收所以如果线程被复用了那么Entry中的Key和Value都不会被回收这样就造成了Key和Value都会发生内存泄漏了</p>
<p>但是设置成弱引用当ThreadLocal对象没有被强引用后就会被回收回收后Entry中的key就会被设置成null了如果Thread被重复使用只要还会用ThreadLocal存储数据那么就会调用ThreadLocal的set、get等方法在调用set、get、等方法的时候是会扫描Entry中key为null的数据的。
当发现Entry中有key为null的数据时会将value也设置为null这样就将value的值也进行了回收能进一步防止内存泄漏了并且在进行rehash的时候也是先清除掉key是null的数据后如果空间还不够才进行扩容的。</p>
<p>但是虽然将key设置了弱引用但是如果一个线程被重复利用执行完任务后再也不使用ThreadLocal了那么最后value值会一直存在最终也是会导致内存泄漏的所以使用ThreadLocal的时候最后一定要执行remove()方法。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/文章/Java中的SPI.md">上一页</a>
</div>
<div style="float: right">
<a href="/文章/Java线程池实现原理及其在美团业务中的实践.md">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997fc46f2b8b66","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>