10000 Java_Guide/docs/java/concurrent/threadlocal.md at master · Ezi1h/Java_Guide · GitHub
[go: up one dir, main page]

Skip to content
< 8000 script type="application/json" data-target="react-app.embeddedData">{"payload":{"allShortcutsEnabled":false,"fileTree":{"docs/java/concurrent":{"items":[{"name":"images","path":"docs/java/concurrent/images","contentType":"directory"},{"name":"aqs原理以及aqs同步组件总结.md","path":"docs/java/concurrent/aqs原理以及aqs同步组件总结.md","contentType":"file"},{"name":"atomic原子类总结.md","path":"docs/java/concurrent/atomic原子类总结.md","contentType":"file"},{"name":"completablefuture-intro.md","path":"docs/java/concurrent/completablefuture-intro.md","contentType":"file"},{"name":"java并发基础常见面试题总结.md","path":"docs/java/concurrent/java并发基础常见面试题总结.md","contentType":"file"},{"name":"java并发进阶常见面试题总结.md","path":"docs/java/concurrent/java并发进阶常见面试题总结.md","contentType":"file"},{"name":"java线程池学习总结.md","path":"docs/java/concurrent/java线程池学习总结.md","contentType":"file"},{"name":"reentrantlock.md","path":"docs/java/concurrent/reentrantlock.md","contentType":"file"},{"name":"threadlocal.md","path":"docs/java/concurrent/threadlocal.md","contentType":"file"},{"name":"并发容器总结.md","path":"docs/java/concurrent/并发容器总结.md","contentType":"file"},{"name":"拿来即用的java线程池最佳实践.md","path":"docs/java/concurrent/拿来即用的java线程池最佳实践.md","contentType":"file"}],"totalCount":11},"docs/java":{"items":[{"name":"basis","path":"docs/java/basis","contentType":"directory"},{"name":"collection","path":"docs/java/collection","contentType":"directory"},{"name":"concurrent","path":"docs/java/concurrent","contentType":"directory"},{"name":"jvm","path":"docs/java/jvm","contentType":"directory"},{"name":"new-features","path":"docs/java/new-features","contentType":"directory"},{"name":"tips","path":"docs/java/tips","contentType":"directory"}],"totalCount":6},"docs":{"items":[{"name":".vuepress","path":"docs/.vuepress","contentType":"directory"},{"name":"about-the-author","path":"docs/about-the-author","contentType":"directory"},{"name":"cs-basics","path":"docs/cs-basics","contentType":"directory"},{"name":"database","path":"docs/database","contentType":"directory"},{"name":"distributed-system","path":"docs/distributed-system","contentType":"directory"},{"name":"high-availability","path":"docs/high-availability","contentType":"directory"},{"name":"high-performance","path":"docs/high-performance","contentType":"directory"},{"name":"idea-tutorial","path":"docs/idea-tutorial","contentType":"directory"},{"name":"java","path":"docs/java","contentType":"directory"},{"name":"system-design","path":"docs/system-design","contentType":"directory"},{"name":"tools","path":"docs/tools","contentType":"directory"},{"name":"readme.md","path":"docs/readme.md","contentType":"file"}],"totalCount":12},"":{"items":[{"name":"docs","path":"docs","contentType":"directory"},{"name":"media","path":"media","contentType":"directory"},{"name":".gitattributes","path":".gitattributes","contentType":"file"},{"name":".gitignore","path":".gitignore","contentType":"file"},{"name":".nojekyll","path":".nojekyll","contentType":"file"},{"name":"README.md","path":"README.md","contentType":"file"},{"name":"index.html","path":"index.html","contentType":"file"},{"name":"package.json","path":"package.json","contentType":"file"},{"name":"sw.js","path":"sw.js","contentType":"file"}],"totalCount":9}},"fileTreeProcessingTime":27.914717,"foldersToFetch":[],"incompleteFileTree":false,"repo":{"id":136123453,"defaultBranch":"master","name":"Java_Guide","ownerLogin":"Ezi1h","currentUserCanPush":false,"isFork":true,"isEmpty":false,"createdAt":"2018-06-05T05:15:25.000Z","ownerAvatar":"https://avatars.githubusercontent.com/u/38790566?v=4","public":true,"private":false,"isOrgOwned":false},"codeLineWrapEnabled":false,"symbolsExpanded":false,"treeExpanded":true,"refInfo":{"name":"master","listCacheKey":"v0:1614147395.450749","canEdit":false,"refType":"branch","currentOid":"e23d71b1580ec6642a98d389623b670f894b3e12"},"path":"docs/java/concurrent/threadlocal.md","currentUser":null,"blob":{"rawLines":null,"stylingDirectives":null,"colorizedLines":null,"csv":null,"csvError":null,"dependabotInfo":{"showConfigurationBanner":false,"configFilePath":null,"networkDependabotPath":"/Ezi1h/Java_Guide/network/updates","dismissConfigurationNoticePath":"/settings/dismiss-notice/dependabot_configuration_notice","configurationNoticeDismissed":null},"displayName":"threadlocal.md","displayUrl":"https://github.com/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/threadlocal.md?raw=true","headerInfo":{"blobSize":"36.4 KB","deleteTooltip":"You must be signed in to make or propose changes","editTooltip":"You must be signed in to make or propose changes","ghDesktopPath":"https://desktop.github.com","isGitLfs":false,"onBranch":true,"shortPath":"a4aff50","siteNavLoginPath":"/login?return_to=https%3A%2F%2Fgithub.com%2FEzi1h%2FJava_Guide%2Fblob%2Fmaster%2Fdocs%2Fjava%2Fconcurrent%2Fthreadlocal.md","isCSV":false,"isRichtext":true,"toc":[{"level":3,"text":"前言","anchor":"前言","htmlText":"前言"},{"level":3,"text":"目录","anchor":"目录","htmlText":"目录"},{"level":3,"text":"ThreadLocal代码演示","anchor":"threadlocal代码演示","htmlText":"ThreadLocal代码演示"},{"level":3,"text":"ThreadLocal的数据结构","anchor":"threadlocal的数据结构","htmlText":"ThreadLocal的数据结构"},{"level":3,"text":"GC 之后 key 是否为 null?","anchor":"gc-之后-key-是否为-null","htmlText":"GC 之后 key 是否为 null?"},{"level":3,"text":"ThreadLocal.set()方法源码详解","anchor":"threadlocalset方法源码详解","htmlText":"ThreadLocal.set()方法源码详解"},{"level":3,"text":"ThreadLocalMap Hash 算法","anchor":"threadlocalmap-hash-算法","htmlText":"ThreadLocalMap Hash 算法"},{"level":3,"text":"ThreadLocalMap Hash 冲突","anchor":"threadlocalmap-hash-冲突","htmlText":"ThreadLocalMap Hash 冲突"},{"level":3,"text":"ThreadLocalMap.set()详解","anchor":"threadlocalmapset详解","htmlText":"ThreadLocalMap.set()详解"},{"level":4,"text":"ThreadLocalMap.set()原理图解","anchor":"threadlocalmapset原理图解","htmlText":"ThreadLocalMap.set()原理图解"},{"level":4,"text":"ThreadLocalMap.set()源码详解","anchor":"threadlocalmapset源码详解","htmlText":"ThreadLocalMap.set()源码详解"},{"level":3,"text":"ThreadLocalMap过期 key 的探测式清理流程","anchor":"threadlocalmap过期-key-的探测式清理流程","htmlText":"ThreadLocalMap过期 key 的探测式清理流程"},{"level":3,"text":"ThreadLocalMap扩容机制","anchor":"threadlocalmap扩容机制","htmlText":"ThreadLocalMap扩容机制"},{"level":3,"text":"ThreadLocalMap.get()详解","anchor":"threadlocalmapget详解","htmlText":"ThreadLocalMap.get()详解"},{"level":4,"text":"ThreadLocalMap.get()图解","anchor":"threadlocalmapget图解","htmlText":"ThreadLocalMap.get()图解"},{"level":4,"text":"ThreadLocalMap.get()源码详解","anchor":"threadlocalmapget源码详解","htmlText":"ThreadLocalMap.get()源码详解"},{"level":3,"text":"ThreadLocalMap过期 key 的启发式清理流程","anchor":"threadlocalmap过期-key-的启发式清理流程","htmlText":"ThreadLocalMap过期 key 的启发式清理流程"},{"level":3,"text":"InheritableThreadLocal","anchor":"inheritablethreadlocal","htmlText":"InheritableThreadLocal"},{"level":3,"text":"ThreadLocal项目中使用实战","anchor":"threadlocal项目中使用实战","htmlText":"ThreadLocal项目中使用实战"},{"level":4,"text":"ThreadLocal使用场景","anchor":"threadlocal使用场景","htmlText":"ThreadLocal使用场景"},{"level":4,"text":"Feign 远程调用解决方案","anchor":"feign-远程调用解决方案","htmlText":"Feign 远程调用解决方案"},{"level":4,"text":"线程池异步调用,requestId 传递","anchor":"线程池异步调用requestid-传递","htmlText":"线程池异步调用,requestId 传递"},{"level":4,"text":"使用 MQ 发送消息给第三方系统","anchor":"使用-mq-发送消息给第三方系统","htmlText":"使用 MQ 发送消息给第三方系统"}],"lineInfo":{"truncatedLoc":"914","truncatedSloc":"649"},"mode":"file"},"image":false,"isCodeownersFile":null,"isPlain":false,"isValidLegacyIssueTemplate":false,"issueTemplate":null,"discussionTemplate":null,"language":"Markdown","languageID":222,"large":false,"planSupportInfo":{"repoIsFork":null,"repoOwnedByCurrentUser":null,"requestFullPath":"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/threadlocal.md","showFreeOrgGatedFeatureMessage":null,"showPlanSupportBanner":null,"upgradeDataAttributes":null,"upgradePath":null},"publishBannersInfo":{"dismissActionNoticePath":"/settings/dismiss-notice/publish_action_from_dockerfile","releasePath":"/Ezi1h/Java_Guide/releases/new?marketplace=true","showPublishActionBanner":false},"rawBlobUrl":"https://github.com/Ezi1h/Java_Guide/raw/refs/heads/master/docs/java/concurrent/threadlocal.md","renderImageOrRaw":false,"richText":"\u003carticle class=\"markdown-body entry-content container-lg\" itemprop=\"text\"\u003e\u003cmarkdown-accessiblity-table\u003e\u003ctable\u003e\n \u003cthead\u003e\n \u003ctr\u003e\n \u003cth\u003etitle\u003c/th\u003e\n \u003cth\u003ecategory\u003c/th\u003e\n \u003cth\u003etag\u003c/th\u003e\n \u003c/tr\u003e\n \u003c/thead\u003e\n \u003ctbody\u003e\n \u003ctr\u003e\n \u003ctd\u003e\u003cdiv dir=\"auto\"\u003e万字解析 ThreadLocal 关键字\u003c/div\u003e\u003c/td\u003e\n \u003ctd\u003e\u003cdiv dir=\"auto\"\u003eJava\u003c/div\u003e\u003c/td\u003e\n \u003ctd\u003e\u003cdiv dir=\"auto\"\u003e\u003ctable\u003e\n \u003ctbody\u003e\n \u003ctr\u003e\n \u003ctd\u003e\u003cdiv dir=\"auto\"\u003eJava并发\u003c/div\u003e\u003c/td\u003e\n \u003c/tr\u003e\n \u003c/tbody\u003e\n\u003c/table\u003e\n\u003c/div\u003e\u003c/td\u003e\n \u003c/tr\u003e\n \u003c/tbody\u003e\n\u003c/table\u003e\u003c/markdown-accessiblity-table\u003e\n\n\u003cblockquote\u003e\n\u003cp dir=\"auto\"\u003e本文来自一枝花算不算浪漫投稿, 原文地址:\u003ca href=\"https://juejin.im/post/5eacc1c75188256d976df748\" rel=\"nofollow\"\u003ehttps://juejin.im/post/5eacc1c75188256d976df748\u003c/a\u003e。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e前言\u003c/h3\u003e\u003ca id=\"user-content-前言\" class=\"anchor\" aria-label=\"Permalink: 前言\" href=\"#前言\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/1.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/1.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003e全文共 10000+字,31 张图,这篇文章同样耗费了不少的时间和精力才创作完成,原创不易,请大家点点关注+在看,感谢。\u003c/strong\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e对于\u003ccode\u003eThreadLocal\u003c/code\u003e,大家的第一反应可能是很简单呀,线程的变量副本,每个线程隔离。那这里有几个问题大家可以思考一下:\u003c/p\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003ccode\u003eThreadLocal\u003c/code\u003e的 key 是\u003cstrong\u003e弱引用\u003c/strong\u003e,那么在 \u003ccode\u003eThreadLocal.get()\u003c/code\u003e的时候,发生\u003cstrong\u003eGC\u003c/strong\u003e之后,key 是否为\u003cstrong\u003enull\u003c/strong\u003e?\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eThreadLocal\u003c/code\u003e中\u003ccode\u003eThreadLocalMap\u003c/code\u003e的\u003cstrong\u003e数据结构\u003c/strong\u003e?\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eThreadLocalMap\u003c/code\u003e的\u003cstrong\u003eHash 算法\u003c/strong\u003e?\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eThreadLocalMap\u003c/code\u003e中\u003cstrong\u003eHash 冲突\u003c/strong\u003e如何解决?\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eThreadLocalMap\u003c/code\u003e的\u003cstrong\u003e扩容机制\u003c/strong\u003e?\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eThreadLocalMap\u003c/code\u003e中\u003cstrong\u003e过期 key 的清理机制\u003c/strong\u003e?\u003cstrong\u003e探测式清理\u003c/strong\u003e和\u003cstrong\u003e启发式清理\u003c/strong\u003e流程?\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eThreadLocalMap.set()\u003c/code\u003e方法实现原理?\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eThreadLocalMap.get()\u003c/code\u003e方法实现原理?\u003c/li\u003e\n\u003cli\u003e项目中\u003ccode\u003eThreadLocal\u003c/code\u003e使用情况?遇到的坑?\u003c/li\u003e\n\u003cli\u003e......\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp dir=\"auto\"\u003e上述的一些问题你是否都已经掌握的很清楚了呢?本文将围绕这些问题使用图文方式来剖析\u003ccode\u003eThreadLocal\u003c/code\u003e的\u003cstrong\u003e点点滴滴\u003c/strong\u003e。\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e目录\u003c/h3\u003e\u003ca id=\"user-content-目录\" class=\"anchor\" aria-label=\"Permalink: 目录\" href=\"#目录\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003e注明:\u003c/strong\u003e 本文源码基于\u003ccode\u003eJDK 1.8\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocal\u003c/code\u003e代码演示\u003c/h3\u003e\u003ca id=\"user-content-threadlocal代码演示\" class=\"anchor\" aria-label=\"Permalink: ThreadLocal代码演示\" href=\"#threadlocal代码演示\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e我们先看下\u003ccode\u003eThreadLocal\u003c/code\u003e使用示例:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"public class ThreadLocalTest {\n private List\u0026lt;String\u0026gt; messages = Lists.newArrayList();\n\n public static final ThreadLocal\u0026lt;ThreadLocalTest\u0026gt; holder = ThreadLocal.withInitial(ThreadLocalTest::new);\n\n public static void add(String message) {\n holder.get().messages.add(message);\n }\n\n public static List\u0026lt;String\u0026gt; clear() {\n List\u0026lt;String\u0026gt; messages = holder.get().messages;\n holder.remove();\n\n System.out.println(\u0026quot;size: \u0026quot; + holder.get().messages.size());\n return messages;\n }\n\n public static void main(String[] args) {\n ThreadLocalTest.add(\u0026quot;一枝花算不算浪漫\u0026quot;);\n System.out.println(holder.get().messages);\n ThreadLocalTest.clear();\n }\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eclass\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eThreadLocalTest\u003c/span\u003e {\n \u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eList\u003c/span\u003e\u0026lt;\u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e\u0026gt; \u003cspan class=\"pl-s1\"\u003emessages\u003c/span\u003e = \u003cspan class=\"pl-smi\"\u003eLists\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003enewArrayList\u003c/span\u003e();\n\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003estatic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efinal\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;\u003cspan class=\"pl-smi\"\u003eThreadLocalTest\u003c/span\u003e\u0026gt; \u003cspan class=\"pl-s1\"\u003eholder\u003c/span\u003e = \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003ewithInitial\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eThreadLocalTest\u003c/span\u003e::\u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e);\n\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003estatic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003eadd\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003emessage\u003c/span\u003e) {\n \u003cspan class=\"pl-s1\"\u003eholder\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e().\u003cspan class=\"pl-s1\"\u003emessages\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eadd\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003emessage\u003c/span\u003e);\n }\n\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003estatic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eList\u003c/span\u003e\u0026lt;\u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e\u0026gt; \u003cspan class=\"pl-en\"\u003eclear\u003c/span\u003e() {\n \u003cspan class=\"pl-smi\"\u003eList\u003c/span\u003e\u0026lt;\u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e\u0026gt; \u003cspan class=\"pl-s1\"\u003emessages\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003eholder\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e().\u003cspan class=\"pl-s1\"\u003emessages\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003eholder\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eremove\u003c/span\u003e();\n\n \u003cspan class=\"pl-smi\"\u003eSystem\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003eout\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eprintln\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"size: \"\u003c/span\u003e + \u003cspan class=\"pl-s1\"\u003eholder\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e().\u003cspan class=\"pl-s1\"\u003emessages\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003esize\u003c/span\u003e());\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003emessages\u003c/span\u003e;\n }\n\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003estatic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003emain\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e[] \u003cspan class=\"pl-s1\"\u003eargs\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eThreadLocalTest\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eadd\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"一枝花算不算浪漫\"\u003c/span\u003e);\n \u003cspan class=\"pl-smi\"\u003eSystem\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003eout\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eprintln\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003eholder\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e().\u003cspan class=\"pl-s1\"\u003emessages\u003c/span\u003e);\n \u003cspan class=\"pl-smi\"\u003eThreadLocalTest\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eclear\u003c/span\u003e();\n }\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e打印结果:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"[一枝花算不算浪漫]\nsize: 0\"\u003e\u003cpre\u003e[\u003cspan class=\"pl-s1\"\u003e一枝花算不算浪漫\u003c/span\u003e]\n\u003cspan class=\"pl-s1\"\u003esize\u003c/span\u003e: \u003cspan class=\"pl-c1\"\u003e0\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003eThreadLocal\u003c/code\u003e对象可以提供线程局部变量,每个线程\u003ccode\u003eThread\u003c/code\u003e拥有一份自己的\u003cstrong\u003e副本变量\u003c/strong\u003e,多个线程互不干扰。\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocal\u003c/code\u003e的数据结构\u003c/h3\u003e\u003ca id=\"user-content-threadlocal的数据结构\" class=\"anchor\" aria-label=\"Permalink: ThreadLocal的数据结构\" href=\"#threadlocal的数据结构\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/2.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/2.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003eThread\u003c/code\u003e类有一个类型为\u003ccode\u003eThreadLocal.ThreadLocalMap\u003c/code\u003e的实例变量\u003ccode\u003ethreadLocals\u003c/code\u003e,也就是说每个线程有一个自己的\u003ccode\u003eThreadLocalMap\u003c/code\u003e。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003eThreadLocalMap\u003c/code\u003e有自己的独立实现,可以简单地将它的\u003ccode\u003ekey\u003c/code\u003e视作\u003ccode\u003eThreadLocal\u003c/code\u003e,\u003ccode\u003evalue\u003c/code\u003e为代码中放入的值(实际上\u003ccode\u003ekey\u003c/code\u003e并不是\u003ccode\u003eThreadLocal\u003c/code\u003e本身,而是它的一个\u003cstrong\u003e弱引用\u003c/strong\u003e)。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e每个线程在往\u003ccode\u003eThreadLocal\u003c/code\u003e里放值的时候,都会往自己的\u003ccode\u003eThreadLocalMap\u003c/code\u003e里存,读也是以\u003ccode\u003eThreadLocal\u003c/code\u003e作为引用,在自己的\u003ccode\u003emap\u003c/code\u003e里找对应的\u003ccode\u003ekey\u003c/code\u003e,从而实现了\u003cstrong\u003e线程隔离\u003c/strong\u003e。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003eThreadLocalMap\u003c/code\u003e有点类似\u003ccode\u003eHashMap\u003c/code\u003e的结构,只是\u003ccode\u003eHashMap\u003c/code\u003e是由\u003cstrong\u003e数组+链表\u003c/strong\u003e实现的,而\u003ccode\u003eThreadLocalMap\u003c/code\u003e中并没有\u003cstrong\u003e链表\u003c/strong\u003e结构。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e我们还要注意\u003ccode\u003eEntry\u003c/code\u003e, 它的\u003ccode\u003ekey\u003c/code\u003e是\u003ccode\u003eThreadLocal\u0026lt;?\u0026gt; k\u003c/code\u003e ,继承自\u003ccode\u003eWeakReference\u003c/code\u003e, 也就是我们常说的弱引用类型。\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eGC 之后 key 是否为 null?\u003c/h3\u003e\u003ca id=\"user-content-gc-之后-key-是否为-null\" class=\"anchor\" aria-label=\"Permalink: GC 之后 key 是否为 null?\" href=\"#gc-之后-key-是否为-null\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e回应开头的那个问题, \u003ccode\u003eThreadLocal\u003c/code\u003e 的\u003ccode\u003ekey\u003c/code\u003e是弱引用,那么在\u003ccode\u003eThreadLocal.get()\u003c/code\u003e的时候,发生\u003ccode\u003eGC\u003c/code\u003e之后,\u003ccode\u003ekey\u003c/code\u003e是否是\u003ccode\u003enull\u003c/code\u003e?\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e为了搞清楚这个问题,我们需要搞清楚\u003ccode\u003eJava\u003c/code\u003e的\u003cstrong\u003e四种引用类型\u003c/strong\u003e:\u003c/p\u003e\n\u003cul dir=\"auto\"\u003e\n\u003cli\u003e\u003cstrong\u003e强引用\u003c/strong\u003e:我们常常 new 出来的对象就是强引用类型,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足的时候\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e软引用\u003c/strong\u003e:使用 SoftReference 修饰的对象被称为软引用,软引用指向的对象在内存要溢出的时候被回收\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e弱引用\u003c/strong\u003e:使用 WeakReference 修饰的对象被称为弱引用,只要发生垃圾回收,若这个对象只被弱引用指向,那么就会被回收\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003e虚引用\u003c/strong\u003e:虚引用是最弱的引用,在 Java 中使用 PhantomReference 进行定义。虚引用中唯一的作用就是用队列接收对象即将死亡的通知\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp dir=\"auto\"\u003e接着再来看下代码,我们使用反射的方式来看看\u003ccode\u003eGC\u003c/code\u003e后\u003ccode\u003eThreadLocal\u003c/code\u003e中的数据情况:(下面代码来源自:\u003ca href=\"https://blog.csdn.net/thewindkee/article/details/103726942\" rel=\"nofollow\"\u003ehttps://blog.csdn.net/thewindkee/article/details/103726942\u003c/a\u003e 本地运行演示 GC 回收场景)\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"public class ThreadLocalDemo {\n\n public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {\n Thread t = new Thread(()-\u0026gt;test(\u0026quot;abc\u0026quot;,false));\n t.start();\n t.join();\n System.out.println(\u0026quot;--gc后--\u0026quot;);\n Thread t2 = new Thread(() -\u0026gt; test(\u0026quot;def\u0026quot;, true));\n t2.start();\n t2.join();\n }\n\n private static void test(String s,boolean isGC) {\n try {\n new ThreadLocal\u0026lt;\u0026gt;().set(s);\n if (isGC) {\n System.gc();\n }\n Thread t = Thread.currentThread();\n Class\u0026lt;? extends Thread\u0026gt; clz = t.getClass();\n Field field = clz.getDeclaredField(\u0026quot;threadLocals\u0026quot;);\n field.setAccessible(true);\n Object ThreadLocalMap = field.get(t);\n Class\u0026lt;?\u0026gt; tlmClass = ThreadLocalMap.getClass();\n Field tableField = tlmClass.getDeclaredField(\u0026quot;table\u0026quot;);\n tableField.setAccessible(true);\n Object[] arr = (Object[]) tableField.get(ThreadLocalMap);\n for (Object o : arr) {\n if (o != null) {\n Class\u0026lt;?\u0026gt; entryClass = o.getClass();\n Field valueField = entryClass.getDeclaredField(\u0026quot;value\u0026quot;);\n Field referenceField = entryClass.getSuperclass().getSuperclass().getDeclaredField(\u0026quot;referent\u0026quot;);\n valueField.setAccessible(true);\n referenceField.setAccessible(true);\n System.out.println(String.format(\u0026quot;弱引用key:%s,值:%s\u0026quot;, referenceField.get(o), valueField.get(o)));\n }\n }\n } catch (Exception e) {\n e.printStackTrace();\n }\n }\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eclass\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eThreadLocalDemo\u003c/span\u003e {\n\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003estatic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003emain\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e[] \u003cspan class=\"pl-s1\"\u003eargs\u003c/span\u003e) \u003cspan class=\"pl-k\"\u003ethrows\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eNoSuchFieldException\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eIllegalAccessException\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eInterruptedException\u003c/span\u003e {\n \u003cspan class=\"pl-smi\"\u003eThread\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003et\u003c/span\u003e = \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eThread\u003c/span\u003e(()-\u0026gt;\u003cspan class=\"pl-en\"\u003etest\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"abc\"\u003c/span\u003e,\u003cspan class=\"pl-c1\"\u003efalse\u003c/span\u003e));\n \u003cspan class=\"pl-s1\"\u003et\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003estart\u003c/span\u003e();\n \u003cspan class=\"pl-s1\"\u003et\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003ejoin\u003c/span\u003e();\n \u003cspan class=\"pl-smi\"\u003eSystem\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003eout\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eprintln\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"--gc后--\"\u003c/span\u003e);\n \u003cspan class=\"pl-smi\"\u003eThread\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003et2\u003c/span\u003e = \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan c 8000 lass=\"pl-smi\"\u003eThread\u003c/span\u003e(() -\u0026gt; \u003cspan class=\"pl-en\"\u003etest\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"def\"\u003c/span\u003e, \u003cspan class=\"pl-c1\"\u003etrue\u003c/span\u003e));\n \u003cspan class=\"pl-s1\"\u003et2\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003estart\u003c/span\u003e();\n \u003cspan class=\"pl-s1\"\u003et2\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003ejoin\u003c/span\u003e();\n }\n\n \u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-k\"\u003estatic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003etest\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003es\u003c/span\u003e,\u003cspan class=\"pl-smi\"\u003eboolean\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eisGC\u003c/span\u003e) {\n \u003cspan class=\"pl-k\"\u003etry\u003c/span\u003e {\n \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;\u0026gt;().\u003cspan class=\"pl-en\"\u003eset\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003es\u003c/span\u003e);\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003eisGC\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eSystem\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003egc\u003c/span\u003e();\n }\n \u003cspan class=\"pl-smi\"\u003eThread\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003et\u003c/span\u003e = \u003cspan class=\"pl-smi\"\u003eThread\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003ecurrentThread\u003c/span\u003e();\n \u003cspan class=\"pl-smi\"\u003eClass\u003c/span\u003e\u0026lt;? \u003cspan class=\"pl-k\"\u003eextends\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eThread\u003c/span\u003e\u0026gt; \u003cspan class=\"pl-s1\"\u003eclz\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003et\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003egetClass\u003c/span\u003e();\n \u003cspan class=\"pl-smi\"\u003eField\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003efield\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003eclz\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003egetDeclaredField\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"threadLocals\"\u003c/span\u003e);\n \u003cspan class=\"pl-s1\"\u003efield\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003esetAccessible\u003c/span\u003e(\u003cspan class=\"pl-c1\"\u003etrue\u003c/span\u003e);\n \u003cspan class=\"pl-smi\"\u003eObject\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eThreadLocalMap\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003efield\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003et\u003c/span\u003e);\n \u003cspan class=\"pl-smi\"\u003eClass\u003c/span\u003e\u0026lt;?\u0026gt; \u003cspan class=\"pl-s1\"\u003etlmClass\u003c/span\u003e = \u003cspan class=\"pl-smi\"\u003eThreadLocalMap\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003egetClass\u003c/span\u003e();\n \u003cspan class=\"pl-smi\"\u003eField\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003etableField\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etlmClass\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003egetDeclaredField\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"table\"\u003c/span\u003e);\n \u003cspan class=\"pl-s1\"\u003etableField\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003esetAccessible\u003c/span\u003e(\u003cspan class=\"pl-c1\"\u003etrue\u003c/span\u003e);\n \u003cspan class=\"pl-smi\"\u003eObject\u003c/span\u003e[] \u003cspan class=\"pl-s1\"\u003earr\u003c/span\u003e = (\u003cspan class=\"pl-smi\"\u003eObject\u003c/span\u003e[]) \u003cspan class=\"pl-s1\"\u003etableField\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003eThreadLocalMap\u003c/span\u003e);\n \u003cspan class=\"pl-k\"\u003efor\u003c/span\u003e (\u003cspan class=\"pl-smi\"\u003eObject\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eo\u003c/span\u003e : \u003cspan class=\"pl-s1\"\u003earr\u003c/span\u003e) {\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003eo\u003c/span\u003e != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eClass\u003c/span\u003e\u0026lt;?\u0026gt; \u003cspan class=\"pl-s1\"\u003eentryClass\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003eo\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003egetClass\u003c/span\u003e();\n \u003cspan class=\"pl-smi\"\u003eField\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003evalueField\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003eentryClass\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003egetDeclaredField\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"value\"\u003c/span\u003e);\n \u003cspan class=\"pl-smi\"\u003eField\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ereferenceField\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003eentryClass\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003egetSuperclass\u003c/span\u003e().\u003cspan class=\"pl-en\"\u003egetSuperclass\u003c/span\u003e().\u003cspan class=\"pl-en\"\u003egetDeclaredField\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"referent\"\u003c/span\u003e);\n \u003cspan class=\"pl-s1\"\u003evalueField\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003esetAccessible\u003c/span\u003e(\u003cspan class=\"pl-c1\"\u003etrue\u003c/span\u003e);\n \u003cspan class=\"pl-s1\"\u003ereferenceField\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003esetAccessible\u003c/span\u003e(\u003cspan class=\"pl-c1\"\u003etrue\u003c/span\u003e);\n \u003cspan class=\"pl-smi\"\u003eSystem\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003eout\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eprintln\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eformat\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"弱引用key:%s,值:%s\"\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003ereferenceField\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003eo\u003c/span\u003e), \u003cspan class=\"pl-s1\"\u003evalueField\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003eo\u003c/span\u003e)));\n }\n }\n } \u003cspan class=\"pl-k\"\u003ecatch\u003c/span\u003e (\u003cspan class=\"pl-smi\"\u003eException\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e) {\n \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eprintStackTrace\u003c/span\u003e();\n }\n }\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e结果如下:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"弱引用key:java.lang.ThreadLocal@433619b6,值:abc\n弱引用key:java.lang.ThreadLocal@418a15e3,值:java.lang.ref.SoftReference@bf97a12\n--gc后--\n弱引用key:null,值:def\"\u003e\u003cpre\u003e\u003cspan class=\"pl-s1\"\u003e弱引用key\u003c/span\u003e:\u003cspan class=\"pl-smi\"\u003ejava\u003c/span\u003e.\u003cspan class=\"pl-smi\"\u003elang\u003c/span\u003e.\u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e@\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e433619\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003eb6\u003c/span\u003e,\u003cspan class=\"pl-s1\"\u003e值\u003c/span\u003e:\u003cspan class=\"pl-smi\"\u003eabc\u003c/span\u003e\n\u003cspan class=\"pl-s1\"\u003e弱引用key\u003c/span\u003e:\u003cspan class=\"pl-smi\"\u003ejava\u003c/span\u003e.\u003cspan class=\"pl-smi\"\u003elang\u003c/span\u003e.\u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e@\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e418\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003ea15e3\u003c/span\u003e,\u003cspan class=\"pl-s1\"\u003e值\u003c/span\u003e:\u003cspan class=\"pl-smi\"\u003ejava\u003c/span\u003e.\u003cspan class=\"pl-smi\"\u003elang\u003c/span\u003e.\u003cspan class=\"pl-smi\"\u003eref\u003c/span\u003e.\u003cspan class=\"pl-smi\"\u003eSoftReference\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003e@\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003ebf97a12\u003c/span\u003e\n--\u003cspan class=\"pl-s1\"\u003egc后\u003c/span\u003e--\n\u003cspan class=\"pl-s1\"\u003e弱引用key\u003c/span\u003e:\u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e,\u003cspan class=\"pl-s1\"\u003e值\u003c/span\u003e:\u003cspan class=\"pl-s1\"\u003edef\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/3.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/3.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e如图所示,因为这里创建的\u003ccode\u003eThreadLocal\u003c/code\u003e并没有指向任何值,也就是没有任何引用:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"new ThreadLocal\u0026lt;\u0026gt;().set(s);\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;\u0026gt;().\u003cspan class=\"pl-en\"\u003eset\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003es\u003c/span\u003e);\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e所以这里在\u003ccode\u003eGC\u003c/code\u003e之后,\u003ccode\u003ekey\u003c/code\u003e就会被回收,我们看到上面\u003ccode\u003edebug\u003c/code\u003e中的\u003ccode\u003ereferent=null\u003c/code\u003e, 如果\u003cstrong\u003e改动一下代码:\u003c/strong\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/4.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/4.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e这个问题刚开始看,如果没有过多思考,\u003cstrong\u003e弱引用\u003c/strong\u003e,还有\u003cstrong\u003e垃圾回收\u003c/strong\u003e,那么肯定会觉得是\u003ccode\u003enull\u003c/code\u003e。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e其实是不对的,因为题目说的是在做 \u003ccode\u003eThreadLocal.get()\u003c/code\u003e 操作,证明其实还是有\u003cstrong\u003e强引用\u003c/strong\u003e存在的,所以 \u003ccode\u003ekey\u003c/code\u003e 并不为 \u003ccode\u003enull\u003c/code\u003e,如下图所示,\u003ccode\u003eThreadLocal\u003c/code\u003e的\u003cstrong\u003e强引用\u003c/strong\u003e仍然是存在的。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/5.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/5.png\" alt=\"image.png\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e如果我们的\u003cstrong\u003e强引用\u003c/strong\u003e不存在的话,那么 \u003ccode\u003ekey\u003c/code\u003e 就会被回收,也就是会出现我们 \u003ccode\u003evalue\u003c/code\u003e 没被回收,\u003ccode\u003ekey\u003c/code\u003e 被回收,导致 \u003ccode\u003evalue\u003c/code\u003e 永远存在,出现内存泄漏。\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocal.set()\u003c/code\u003e方法源码详解\u003c/h3\u003e\u003ca id=\"user-content-threadlocalset方法源码详解\" class=\"anchor\" aria-label=\"Permalink: ThreadLocal.set()方法源码详解\" href=\"#threadlocalset方法源码详解\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/6.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/6.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003eThreadLocal\u003c/code\u003e中的\u003ccode\u003eset\u003c/code\u003e方法原理如上图所示,很简单,主要是判断\u003ccode\u003eThreadLocalMap\u003c/code\u003e是否存在,然后使用\u003ccode\u003eThreadLocal\u003c/code\u003e中的\u003ccode\u003eset\u003c/code\u003e方法进行数据处理。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e代码如下:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"public void set(T value) {\n Thread t = Thread.currentThread();\n ThreadLocalMap map = getMap(t);\n if (map != null)\n map.set(this, value);\n else\n createMap(t, value);\n}\n\nvoid createMap(Thread t, T firstValue) {\n t.threadLocals = new ThreadLocalMap(this, firstValue);\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003eset\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eT\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eThread\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003et\u003c/span\u003e = \u003cspan class=\"pl-smi\"\u003eThread\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003ecurrentThread\u003c/span\u003e();\n \u003cspan class=\"pl-smi\"\u003eThreadLocalMap\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003emap\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003egetMap\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003et\u003c/span\u003e);\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003emap\u003c/span\u003e != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e)\n \u003cspan class=\"pl-s1\"\u003emap\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eset\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e);\n \u003cspan class=\"pl-k\"\u003eelse\u003c/span\u003e\n \u003cspan class=\"pl-en\"\u003ecreateMap\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003et\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e);\n}\n\n\u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003ecreateMap\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eThread\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003et\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eT\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003efirstValue\u003c/span\u003e) {\n \u003cspan class=\"pl-s1\"\u003et\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003ethreadLocals\u003c/span\u003e = \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eThreadLocalMap\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003efirstValue\u003c/span\u003e);\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e主要的核心逻辑还是在\u003ccode\u003eThreadLocalMap\u003c/code\u003e中的,一步步往下看,后面还有更详细的剖析。\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocalMap\u003c/code\u003e Hash 算法\u003c/h3\u003e\u003ca id=\"user-content-threadlocalmap-hash-算法\" class=\"anchor\" aria-label=\"Permalink: ThreadLocalMap Hash 算法\" href=\"#threadlocalmap-hash-算法\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e既然是\u003ccode\u003eMap\u003c/code\u003e结构,那么\u003ccode\u003eThreadLocalMap\u003c/code\u003e当然也要实现自己的\u003ccode\u003ehash\u003c/code\u003e算法来解决散列表数组冲突问题。\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"int i = key.threadLocalHashCode \u0026amp; (len-1);\"\u003e\u003cpre\u003e\u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003ethreadLocalHashCode\u003c/span\u003e \u0026amp; (\u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e-\u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e);\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003eThreadLocalMap\u003c/code\u003e中\u003ccode\u003ehash\u003c/code\u003e算法很简单,这里\u003ccode\u003ei\u003c/code\u003e就是当前 key 在散列表中对应的数组下标位置。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e这里最关键的就是\u003ccode\u003ethreadLocalHashCode\u003c/code\u003e值的计算,\u003ccode\u003eThreadLocal\u003c/code\u003e中有一个属性为\u003ccode\u003eHASH_INCREMENT = 0x61c88647\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"public class ThreadLocal\u0026lt;T\u0026gt; {\n private final int threadLocalHashCode = nextHashCode();\n\n private static AtomicInteger nextHashCode = new AtomicInteger();\n\n private static final int HASH_INCREMENT = 0x61c88647;\n\n private static int nextHashCode() {\n return nextHashCode.getAndAdd(HASH_INCREMENT);\n }\n\n static class ThreadLocalMap {\n ThreadLocalMap(ThreadLocal\u0026lt;?\u0026gt; firstKey, Object firstValue) {\n table = new Entry[INITIAL_CAPACITY];\n int i = firstKey.threadLocalHashCode \u0026amp; (INITIAL_CAPACITY - 1);\n\n table[i] = new Entry(firstKey, firstValue);\n size = 1;\n setThreshold(INITIAL_CAPACITY);\n }\n }\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eclass\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;\u003cspan class=\"pl-smi\"\u003eT\u003c/span\u003e\u0026gt; {\n \u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efinal\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ethreadLocalHashCode\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003enextHashCode\u003c/span\u003e();\n\n \u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-k\"\u003estatic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eAtomicInteger\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003enextHashCode\u003c/span\u003e = \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eAtomicInteger\u003c/span\u003e();\n\n \u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-k\"\u003estatic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003efinal\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003eHASH_INCREMENT\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003e0x61c88647\u003c/span\u003e;\n\n \u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-k\"\u003estatic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-en\"\u003enextHashCode\u003c/span\u003e() {\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003enextHashCode\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003egetAndAdd\u003c/span\u003e(\u003cspan class=\"pl-c1\"\u003eHASH_INCREMENT\u003c/span\u003e);\n }\n\n \u003cspan class=\"pl-k\"\u003estatic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eclass\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eThreadLocalMap\u003c/span\u003e {\n \u003cspan class=\"pl-smi\"\u003eThreadLocalMap\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;?\u0026gt; \u003cspan class=\"pl-s1\"\u003efirstKey\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eObject\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003efirstValue\u003c/span\u003e) {\n \u003cspan class=\"pl-s1\"\u003etable\u003c/span\u003e = \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e[\u003cspan class=\"pl-c1\"\u003eINITIAL_CAPACITY\u003c/span\u003e];\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003efirstKey\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003ethreadLocalHashCode\u003c/span\u003e \u0026amp; (\u003cspan class=\"pl-c1\"\u003eINITIAL_CAPACITY\u003c/span\u003e - \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e);\n\n \u003cspan class=\"pl-s1\"\u003etable\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e] = \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003efirstKey\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003efirstValue\u003c/span\u003e);\n \u003cspan class=\"pl-s1\"\u003esize\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e;\n \u003cspan class=\"pl-en\"\u003esetThreshold\u003c/span\u003e(\u003cspan class=\"pl-c1\"\u003eINITIAL_CAPACITY\u003c/span\u003e);\n }\n }\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e每当创建一个\u003ccode\u003eThreadLocal\u003c/code\u003e对象,这个\u003ccode\u003eThreadLocal.nextHashCode\u003c/code\u003e 这个值就会增长 \u003ccode\u003e0x61c88647\u003c/code\u003e 。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e这个值很特殊,它是\u003cstrong\u003e斐波那契数\u003c/strong\u003e 也叫 \u003cstrong\u003e黄金分割数\u003c/strong\u003e。\u003ccode\u003ehash\u003c/code\u003e增量为 这个数字,带来的好处就是 \u003ccode\u003ehash\u003c/code\u003e \u003cstrong\u003e分布非常均匀\u003c/strong\u003e。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e我们自己可以尝试下:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/8.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/8.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e可以看到产生的哈希码分布很均匀,这里不去细纠\u003cstrong\u003e斐波那契\u003c/strong\u003e具体算法,感兴趣的可以自行查阅相关资料。\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocalMap\u003c/code\u003e Hash 冲突\u003c/h3\u003e\u003ca id=\"user-content-threadlocalmap-hash-冲突\" class=\"anchor\" aria-label=\"Permalink: ThreadLocalMap Hash 冲突\" href=\"#threadlocalmap-hash-冲突\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cblockquote\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003e注明:\u003c/strong\u003e 下面所有示例图中,\u003cstrong\u003e绿色块\u003c/strong\u003e\u003ccode\u003eEntry\u003c/code\u003e代表\u003cstrong\u003e正常数据\u003c/strong\u003e,\u003cstrong\u003e灰色块\u003c/strong\u003e代表\u003ccode\u003eEntry\u003c/code\u003e的\u003ccode\u003ekey\u003c/code\u003e值为\u003ccode\u003enull\u003c/code\u003e,\u003cstrong\u003e已被垃圾回收\u003c/strong\u003e。\u003cstrong\u003e白色块\u003c/strong\u003e表示\u003ccode\u003eEntry\u003c/code\u003e为\u003ccode\u003enull\u003c/code\u003e。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp dir=\"auto\"\u003e虽然\u003ccode\u003eThreadLocalMap\u003c/code\u003e中使用了\u003cstrong\u003e黄金分割数\u003c/strong\u003e来作为\u003ccode\u003ehash\u003c/code\u003e计算因子,大大减少了\u003ccode\u003eHash\u003c/code\u003e冲突的概率,但是仍然会存在冲突。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003eHashMap\u003c/code\u003e中解决冲突的方法是在数组上构造一个\u003cstrong\u003e链表\u003c/strong\u003e结构,冲突的数据挂载到链表上,如果链表长度超过一定数量则会转化成\u003cstrong\u003e红黑树\u003c/strong\u003e。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e而 \u003ccode\u003eThreadLocalMap\u003c/code\u003e 中并没有链表结构,所以这里不能使用 \u003ccode\u003eHashMap\u003c/code\u003e 解决冲突的方式了。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/7.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/7.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e如上图所示,如果我们插入一个\u003ccode\u003evalue=27\u003c/code\u003e的数据,通过 \u003ccode\u003ehash\u003c/code\u003e 计算后应该落入槽位 4 中,而槽位 4 已经有了 \u003ccode\u003eEntry\u003c/code\u003e 数据。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e此时就会线性向后查找,一直找到 \u003ccode\u003eEntry\u003c/code\u003e 为 \u003ccode\u003enull\u003c/code\u003e 的槽位才会停止查找,将当前元素放入此槽位中。当然迭代过程中还有其他的情况,比如遇到了 \u003ccode\u003eEntry\u003c/code\u003e 不为 \u003ccode\u003enull\u003c/code\u003e 且 \u003ccode\u003ekey\u003c/code\u003e 值相等的情况,还有 \u003ccode\u003eEntry\u003c/code\u003e 中的 \u003ccode\u003ekey\u003c/code\u003e 值为 \u003ccode\u003enull\u003c/code\u003e 的情况等等都会有不同的处理,后面会一一详细讲解。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e这里还画了一个\u003ccode\u003eEntry\u003c/code\u003e中的\u003ccode\u003ekey\u003c/code\u003e为\u003ccode\u003enull\u003c/code\u003e的数据(\u003cstrong\u003eEntry=2 的灰色块数据\u003c/strong\u003e),因为\u003ccode\u003ekey\u003c/code\u003e值是\u003cstrong\u003e弱引用\u003c/strong\u003e类型,所以会有这种数据存在。在\u003ccode\u003eset\u003c/code\u003e过程中,如果遇到了\u003ccode\u003ekey\u003c/code\u003e过期的\u003ccode\u003eEntry\u003c/code\u003e数据,实际上是会进行一轮\u003cstrong\u003e探测式清理\u003c/strong\u003e操作的,具体操作方式后面会讲到。\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocalMap.set()\u003c/code\u003e详解\u003c/h3\u003e\u003ca id=\"user-content-threadlocalmapset详解\" class=\"anchor\" aria-label=\"Permalink: ThreadLocalMap.set()详解\" href=\"#threadlocalmapset详解\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocalMap.set()\u003c/code\u003e原理图解\u003c/h4\u003e\u003ca id=\"user-content-threadlocalmapset原理图解\" class=\"anchor\" aria-label=\"Permalink: ThreadLocalMap.set()原理图解\" href=\"#threadlocalmapset原理图解\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e看完了\u003ccode\u003eThreadLocal\u003c/code\u003e \u003cstrong\u003ehash 算法\u003c/strong\u003e后,我们再来看\u003ccode\u003eset\u003c/code\u003e是如何实现的。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e往\u003ccode\u003eThreadLocalMap\u003c/code\u003e中\u003ccode\u003eset\u003c/code\u003e数据(\u003cstrong\u003e新增\u003c/strong\u003e或者\u003cstrong\u003e更新\u003c/strong\u003e数据)分为好几种情况,针对不同的情况我们画图来说明。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003e第一种情况:\u003c/strong\u003e 通过\u003ccode\u003ehash\u003c/code\u003e计算后的槽位对应的\u003ccode\u003eEntry\u003c/code\u003e数据为空:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-lo 8000 cal/9.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/9.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e这里直接将数据放到该槽位即可。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003e第二种情况:\u003c/strong\u003e 槽位数据不为空,\u003ccode\u003ekey\u003c/code\u003e值与当前\u003ccode\u003eThreadLocal\u003c/code\u003e通过\u003ccode\u003ehash\u003c/code\u003e计算获取的\u003ccode\u003ekey\u003c/code\u003e值一致:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/10.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/10.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e这里直接更新该槽位的数据。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003e第三种情况:\u003c/strong\u003e 槽位数据不为空,往后遍历过程中,在找到\u003ccode\u003eEntry\u003c/code\u003e为\u003ccode\u003enull\u003c/code\u003e的槽位之前,没有遇到\u003ccode\u003ekey\u003c/code\u003e过期的\u003ccode\u003eEntry\u003c/code\u003e:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/11.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/11.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e遍历散列数组,线性往后查找,如果找到\u003ccode\u003eEntry\u003c/code\u003e为\u003ccode\u003enull\u003c/code\u003e的槽位,则将数据放入该槽位中,或者往后遍历过程中,遇到了\u003cstrong\u003ekey 值相等\u003c/strong\u003e的数据,直接更新即可。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003e第四种情况:\u003c/strong\u003e 槽位数据不为空,往后遍历过程中,在找到\u003ccode\u003eEntry\u003c/code\u003e为\u003ccode\u003enull\u003c/code\u003e的槽位之前,遇到\u003ccode\u003ekey\u003c/code\u003e过期的\u003ccode\u003eEntry\u003c/code\u003e,如下图,往后遍历过程中,遇到了\u003ccode\u003eindex=7\u003c/code\u003e的槽位数据\u003ccode\u003eEntry\u003c/code\u003e的\u003ccode\u003ekey=null\u003c/code\u003e:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/12.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/12.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e散列数组下标为 7 位置对应的\u003ccode\u003eEntry\u003c/code\u003e数据\u003ccode\u003ekey\u003c/code\u003e为\u003ccode\u003enull\u003c/code\u003e,表明此数据\u003ccode\u003ekey\u003c/code\u003e值已经被垃圾回收掉了,此时就会执行\u003ccode\u003ereplaceStaleEntry()\u003c/code\u003e方法,该方法含义是\u003cstrong\u003e替换过期数据的逻辑\u003c/strong\u003e,以\u003cstrong\u003eindex=7\u003c/strong\u003e位起点开始遍历,进行探测式数据清理工作。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e初始化探测式清理过期数据扫描的开始位置:\u003ccode\u003eslotToExpunge = staleSlot = 7\u003c/code\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e以当前\u003ccode\u003estaleSlot\u003c/code\u003e开始 向前迭代查找,找其他过期的数据,然后更新过期数据起始扫描下标\u003ccode\u003eslotToExpunge\u003c/code\u003e。\u003ccode\u003efor\u003c/code\u003e循环迭代,直到碰到\u003ccode\u003eEntry\u003c/code\u003e为\u003ccode\u003enull\u003c/code\u003e结束。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e如果找到了过期的数据,继续向前迭代,直到遇到\u003ccode\u003eEntry=null\u003c/code\u003e的槽位才停止迭代,如下图所示,\u003cstrong\u003eslotToExpunge 被更新为 0\u003c/strong\u003e:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/13.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/13.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e以当前节点(\u003ccode\u003eindex=7\u003c/code\u003e)向前迭代,检测是否有过期的\u003ccode\u003eEntry\u003c/code\u003e数据,如果有则更新\u003ccode\u003eslotToExpunge\u003c/code\u003e值。碰到\u003ccode\u003enull\u003c/code\u003e则结束探测。以上图为例\u003ccode\u003eslotToExpunge\u003c/code\u003e被更新为 0。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e上面向前迭代的操作是为了更新探测清理过期数据的起始下标\u003ccode\u003eslotToExpunge\u003c/code\u003e的值,这个值在后面会讲解,它是用来判断当前过期槽位\u003ccode\u003estaleSlot\u003c/code\u003e之前是否还有过期元素。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e接着开始以\u003ccode\u003estaleSlot\u003c/code\u003e位置(\u003ccode\u003eindex=7\u003c/code\u003e)向后迭代,\u003cstrong\u003e如果找到了相同 key 值的 Entry 数据:\u003c/strong\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/14.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/14.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e从当前节点\u003ccode\u003estaleSlot\u003c/code\u003e向后查找\u003ccode\u003ekey\u003c/code\u003e值相等的\u003ccode\u003eEntry\u003c/code\u003e元素,找到后更新\u003ccode\u003eEntry\u003c/code\u003e的值并交换\u003ccode\u003estaleSlot\u003c/code\u003e元素的位置(\u003ccode\u003estaleSlot\u003c/code\u003e位置为过期元素),更新\u003ccode\u003eEntry\u003c/code\u003e数据,然后开始进行过期\u003ccode\u003eEntry\u003c/code\u003e的清理工作,如下图所示:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer nofollow\" href=\"https://camo.githubusercontent.com/7becf8a39a26397d81e164182e6f33003742b46f179fee8d9d2de058248a4b01/68747470733a2f2f67756964652d626c6f672d696d616765732e6f73732d636e2d7368656e7a68656e2e616c6979756e63732e636f6d2f6a6176612d67756964652d626c6f672f766965772e706e67\"\u003e\u003cimg src=\"https://camo.githubusercontent.com/7becf8a39a26397d81e164182e6f33003742b46f179fee8d9d2de058248a4b01/68747470733a2f2f67756964652d626c6f672d696d616765732e6f73732d636e2d7368656e7a68656e2e616c6979756e63732e636f6d2f6a6176612d67756964652d626c6f672f766965772e706e67\" alt=\"\" data-canonical-src=\"https://guide-blog-images.oss-cn-shenzhen.aliyuncs.com/java-guide-blog/view.png\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e向后遍历过程中,如果没有找到相同 key 值的 Entry 数据:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/15.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/15.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e从当前节点\u003ccode\u003estaleSlot\u003c/code\u003e向后查找\u003ccode\u003ekey\u003c/code\u003e值相等的\u003ccode\u003eEntry\u003c/code\u003e元素,直到\u003ccode\u003eEntry\u003c/code\u003e为\u003ccode\u003enull\u003c/code\u003e则停止寻找。通过上图可知,此时\u003ccode\u003etable\u003c/code\u003e中没有\u003ccode\u003ekey\u003c/code\u003e值相同的\u003ccode\u003eEntry\u003c/code\u003e。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e创建新的\u003ccode\u003eEntry\u003c/code\u003e,替换\u003ccode\u003etable[stableSlot]\u003c/code\u003e位置:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/16.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/16.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e替换完成后也是进行过期元素清理工作,清理工作主要是有两个方法:\u003ccode\u003eexpungeStaleEntry()\u003c/code\u003e和\u003ccode\u003ecleanSomeSlots()\u003c/code\u003e,具体细节后面会讲到,请继续往后看。\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocalMap.set()\u003c/code\u003e源码详解\u003c/h4\u003e\u003ca id=\"user-content-threadlocalmapset源码详解\" class=\"anchor\" aria-label=\"Permalink: ThreadLocalMap.set()源码详解\" href=\"#threadlocalmapset源码详解\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e上面已经用图的方式解析了\u003ccode\u003eset()\u003c/code\u003e实现的原理,其实已经很清晰了,我们接着再看下源码:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003ejava.lang.ThreadLocal\u003c/code\u003e.\u003ccode\u003eThreadLocalMap.set()\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"private void set(ThreadLocal\u0026lt;?\u0026gt; key, Object value) {\n Entry[] tab = table;\n int len = tab.length;\n int i = key.threadLocalHashCode \u0026amp; (len-1);\n\n for (Entry e = tab[i];\n e != null;\n e = tab[i = nextIndex(i, len)]) {\n ThreadLocal\u0026lt;?\u0026gt; k = e.get();\n\n if (k == key) {\n e.value = value;\n return;\n }\n\n if (k == null) {\n replaceStaleEntry(key, value, i);\n return;\n }\n }\n\n tab[i] = new Entry(key, value);\n int sz = ++size;\n if (!cleanSomeSlots(i, sz) \u0026amp;\u0026amp; sz \u0026gt;= threshold)\n rehash();\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003eset\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;?\u0026gt; \u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eObject\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e[] \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etable\u003c/span\u003e;\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003elength\u003c/span\u003e;\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003ethreadLocalHashCode\u003c/span\u003e \u0026amp; (\u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e-\u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e);\n\n \u003cspan class=\"pl-k\"\u003efor\u003c/span\u003e (\u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e];\n \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003enextIndex\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e)]) {\n \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;?\u0026gt; \u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e();\n\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e == \u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e) {\n \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e;\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e;\n }\n\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e == \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e) {\n \u003cspan class=\"pl-en\"\u003ereplaceStaleEntry\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e);\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e;\n }\n }\n\n \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e] = \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e);\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003esz\u003c/span\u003e = ++\u003cspan class=\"pl-s1\"\u003esize\u003c/span\u003e;\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (!\u003cspan class=\"pl-en\"\u003ecleanSomeSlots\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003esz\u003c/span\u003e) \u0026amp;\u0026amp; \u003cspan class=\"pl-s1\"\u003esz\u003c/span\u003e \u0026gt;= \u003cspan class=\"pl-s1\"\u003ethreshold\u003c/span\u003e)\n \u003cspan class=\"pl-en\"\u003erehash\u003c/span\u003e();\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e这里会通过\u003ccode\u003ekey\u003c/code\u003e来计算在散列表中的对应位置,然后以当前\u003ccode\u003ekey\u003c/code\u003e对应的桶的位置向后查找,找到可以使用的桶。\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"Entry[] tab = table;\nint len = tab.length;\nint i = key.threadLocalHashCode \u0026amp; (len-1);\"\u003e\u003cpre\u003e\u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e[] \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etable\u003c/span\u003e;\n\u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003elength\u003c/span\u003e;\n\u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003ethreadLocalHashCode\u003c/span\u003e \u0026amp; (\u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e-\u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e);\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e什么情况下桶才是可以使用的呢?\u003c/p\u003e\n\u003col dir=\"auto\"\u003e\n\u003cli\u003e\u003ccode\u003ek = key\u003c/code\u003e 说明是替换操作,可以使用\u003c/li\u003e\n\u003cli\u003e碰到一个过期的桶,执行替换逻辑,占用过期桶\u003c/li\u003e\n\u003cli\u003e查找过程中,碰到桶中\u003ccode\u003eEntry=null\u003c/code\u003e的情况,直接使用\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp dir=\"auto\"\u003e接着就是执行\u003ccode\u003efor\u003c/code\u003e循环遍历,向后查找,我们先看下\u003ccode\u003enextIndex()\u003c/code\u003e、\u003ccode\u003eprevIndex()\u003c/code\u003e方法实现:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/17.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/17.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"private static int nextIndex(int i, int len) {\n return ((i + 1 \u0026lt; len) ? i + 1 : 0);\n}\n\nprivate static int prevIndex(int i, int len) {\n return ((i - 1 \u0026gt;= 0) ? i - 1 : len - 1);\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-k\"\u003estatic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-en\"\u003enextIndex\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e) {\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e ((\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e + \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e \u0026lt; \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e) ? \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e + \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e : \u003cspan class=\"pl-c1\"\u003e0\u003c/span\u003e);\n}\n\n\u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-k\"\u003estatic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-en\"\u003eprevIndex\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e) {\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e ((\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e - \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e \u0026gt;= \u003cspan class=\"pl-c1\"\u003e0\u003c/span\u003e) ? \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e - \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e : \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e - \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e);\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e接着看剩下\u003ccode\u003efor\u003c/code\u003e循环中的逻辑:\u003c/p\u003e\n\u003col dir=\"auto\"\u003e\n\u003cli\u003e遍历当前\u003ccode\u003ekey\u003c/code\u003e值对应的桶中\u003ccode\u003eEntry\u003c/code\u003e数据为空,这说明散列数组这里没有数据冲突,跳出\u003ccode\u003efor\u003c/code\u003e循环,直接\u003ccode\u003eset\u003c/code\u003e数据到对应的桶中\u003c/li\u003e\n\u003cli\u003e如果\u003ccode\u003ekey\u003c/code\u003e值对应的桶中\u003ccode\u003eEntry\u003c/code\u003e数据不为空\u003cbr\u003e\n2.1 如果\u003ccode\u003ek = key\u003c/code\u003e,说明当前\u003ccode\u003eset\u003c/code\u003e操作是一个替换操作,做替换逻辑,直接返回\u003cbr\u003e\n2.2 如果\u003ccode\u003ekey = null\u003c/code\u003e,说明当前桶位置的\u003ccode\u003eEntry\u003c/code\u003e是过期数据,执行\u003ccode\u003ereplaceStaleEntry()\u003c/code\u003e方法(核心方法),然后返回\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003efor\u003c/code\u003e循环执行完毕,继续往下执行说明向后迭代的过程中遇到了\u003ccode\u003eentry\u003c/code\u003e为\u003ccode\u003enull\u003c/code\u003e的情况\u003cbr\u003e\n3.1 在\u003ccode\u003eEntry\u003c/code\u003e为\u003ccode\u003enull\u003c/code\u003e的桶中创建一个新的\u003ccode\u003eEntry\u003c/code\u003e对象\u003cbr\u003e\n3.2 执行\u003ccode\u003e++size\u003c/code\u003e操作\u003c/li\u003e\n\u003cli\u003e调用\u003ccode\u003ecleanSomeSlots()\u003c/code\u003e做一次启发式清理工作,清理散列数组中\u003ccode\u003eEntry\u003c/code\u003e的\u003ccode\u003ekey\u003c/code\u003e过期的数据\u003cbr\u003e\n4.1 如果清理工作完成后,未清理到任何数据,且\u003ccode\u003esize\u003c/code\u003e超过了阈值(数组长度的 2/3),进行\u003ccode\u003erehash()\u003c/code\u003e操作\u003cbr\u003e\n4.2 \u003ccode\u003erehash()\u003c/code\u003e中会先进行一轮探测式清理,清理过期\u003ccode\u003ekey\u003c/code\u003e,清理完成后如果\u003cstrong\u003esize \u0026gt;= threshold - threshold / 4\u003c/strong\u003e,就会执行真正的扩容逻辑(扩容逻辑往后看)\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp dir=\"auto\"\u003e接着重点看下\u003ccode\u003ereplaceStaleEntry()\u003c/code\u003e方法,\u003ccode\u003ereplaceStaleEntry()\u003c/code\u003e方法提供替换过期数据的功能,我们可以对应上面\u003cstrong\u003e第四种情况\u003c/strong\u003e的原理图来再回顾下,具体代码如下:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003ejava.lang.ThreadLocal.ThreadLocalMap.replaceStaleEntry()\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"private void replaceStaleEntry(ThreadLocal\u0026lt;?\u0026gt; key, Object value,\n int staleSlot) {\n Entry[] tab = table;\n int len = tab.length;\n Entry e;\n\n int slotToExpunge = staleSlot;\n for (int i = prevIndex(staleSlot, len);\n (e = tab[i]) != null;\n i = prevIndex(i, len))\n\n if (e.get() == null)\n slotToExpunge = i;\n\n for (int i = nextIndex(staleSlot, len);\n (e = tab[i]) != null;\n i = nextIndex(i, len)) {\n\n ThreadLocal\u0026lt;?\u0026gt; k = e.get();\n\n if (k == key) {\n e.value = value;\n\n tab[i] = tab[staleSlot];\n tab[staleSlot] = e;\n\n if (slotToExpunge == staleSlot)\n slotToExpunge = i;\n cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);\n return;\n }\n\n if (k == null \u0026amp;\u0026amp; slotToExpunge == staleSlot)\n slotToExpunge = i;\n }\n\n tab[staleSlot].value = null;\n tab[staleSlot] = new Entry(key, value);\n\n if (slotToExpunge != staleSlot)\n cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003ereplaceStaleEntry\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;?\u0026gt; \u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eObject\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e,\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e[] \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etable\u003c/span\u003e;\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003elength\u003c/span\u003e;\n \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e;\n\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e;\n \u003cspan class=\"pl-k\"\u003efor\u003c/span\u003e (\u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003eprevIndex\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e);\n (\u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e]) != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003eprevIndex\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e))\n\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e() == \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e)\n \u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e;\n\n \u003cspan class=\"pl-k\"\u003efor\u003c/span\u003e (\u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003enextIndex\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e);\n (\u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e]) != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003enextIndex\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e)) {\n\n \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;?\u0026gt; \u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e();\n\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e == \u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e) {\n \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e;\n\n \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e] = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e];\n \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e] = \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e;\n\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e == \u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e)\n \u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e;\n \u003cspan class=\"pl-en\"\u003ecleanSomeSlots\u003c/span\u003e(\u003cspan class=\"pl-en\"\u003eexpungeStaleEntry\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e), \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e);\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e;\n }\n\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e == \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e \u0026amp;\u0026amp; \u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e == \u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e)\n \u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e;\n }\n\n \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e].\u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e] = \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e);\n\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e != \u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e)\n \u003cspan class=\"pl-en\"\u003ecleanSomeSlots\u003c/span\u003e(\u003cspan class=\"pl-en\"\u003eexpungeStaleEntry\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e), \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e);\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003eslotToExpunge\u003c/code\u003e表示开始探测式清理过期数据的开始下标,默认从当前的\u003ccode\u003estaleSlot\u003c/code\u003e开始。以当前的\u003ccode\u003estaleSlot\u003c/code\u003e开始,向前迭代查找,找到没有过期的数据,\u003ccode\u003efor\u003c/code\u003e循环一直碰到\u003ccode\u003eEntry\u003c/code\u003e为\u003ccode\u003enull\u003c/code\u003e才会结束。如果向前找到了过期数据,更新探测清理过期数据的开始下标为 i,即\u003ccode\u003eslotToExpunge=i\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"for (int i = prevIndex(staleSlot, len);\n (e = tab[i]) != null;\n i = prevIndex(i, len)){\n\n if (e.get() == null){\n slotToExpunge = i;\n }\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003efor\u003c/span\u003e (\u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003eprevIndex\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e);\n (\u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e]) != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003eprevIndex\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e)){\n\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e() == \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e){\n \u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e;\n }\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e接着开始从\u003ccode\u003estaleSlot\u003c/code\u003e向后查找,也是碰到\u003ccode\u003eEntry\u003c/code\u003e为\u003ccode\u003enull\u003c/code\u003e的桶结束。\n如果迭代过程中,\u003cstrong\u003e碰到 k == key\u003c/strong\u003e,这说明这里是 8000 换逻辑,替换新数据并且交换当前\u003ccode\u003estaleSlot\u003c/code\u003e位置。如果\u003ccode\u003eslotToExpunge == staleSlot\u003c/code\u003e,这说明\u003ccode\u003ereplaceStaleEntry()\u003c/code\u003e一开始向前查找过期数据时并未找到过期的\u003ccode\u003eEntry\u003c/code\u003e数据,接着向后查找过程中也未发现过期数据,修改开始探测式清理过期数据的下标为当前循环的 index,即\u003ccode\u003eslotToExpunge = i\u003c/code\u003e。最后调用\u003ccode\u003ecleanSomeSlots(expungeStaleEntry(slotToExpunge), len);\u003c/code\u003e进行启发式过期数据清理。\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"if (k == key) {\n e.value = value;\n\n tab[i] = tab[staleSlot];\n tab[staleSlot] = e;\n\n if (slotToExpunge == staleSlot)\n slotToExpunge = i;\n\n cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);\n return;\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e == \u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e) {\n \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e;\n\n \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e] = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e];\n \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e] = \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e;\n\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e == \u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e)\n \u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e;\n\n \u003cspan class=\"pl-en\"\u003ecleanSomeSlots\u003c/span\u003e(\u003cspan class=\"pl-en\"\u003eexpungeStaleEntry\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e), \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e);\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e;\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003ecleanSomeSlots()\u003c/code\u003e和\u003ccode\u003eexpungeStaleEntry()\u003c/code\u003e方法后面都会细讲,这两个是和清理相关的方法,一个是过期\u003ccode\u003ekey\u003c/code\u003e相关\u003ccode\u003eEntry\u003c/code\u003e的启发式清理(\u003ccode\u003eHeuristically scan\u003c/code\u003e),另一个是过期\u003ccode\u003ekey\u003c/code\u003e相关\u003ccode\u003eEntry\u003c/code\u003e的探测式清理。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003e如果 k != key\u003c/strong\u003e则会接着往下走,\u003ccode\u003ek == null\u003c/code\u003e说明当前遍历的\u003ccode\u003eEntry\u003c/code\u003e是一个过期数据,\u003ccode\u003eslotToExpunge == staleSlot\u003c/code\u003e说明,一开始的向前查找数据并未找到过期的\u003ccode\u003eEntry\u003c/code\u003e。如果条件成立,则更新\u003ccode\u003eslotToExpunge\u003c/code\u003e 为当前位置,这个前提是前驱节点扫描时未发现过期数据。\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"if (k == null \u0026amp;\u0026amp; slotToExpunge == staleSlot)\n slotToExpunge = i;\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e == \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e \u0026amp;\u0026amp; \u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e == \u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e)\n \u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e;\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e往后迭代的过程中如果没有找到\u003ccode\u003ek == key\u003c/code\u003e的数据,且碰到\u003ccode\u003eEntry\u003c/code\u003e为\u003ccode\u003enull\u003c/code\u003e的数据,则结束当前的迭代操作。此时说明这里是一个添加的逻辑,将新的数据添加到\u003ccode\u003etable[staleSlot]\u003c/code\u003e 对应的\u003ccode\u003eslot\u003c/code\u003e中。\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"tab[staleSlot].value = null;\ntab[staleSlot] = new Entry(key, value);\"\u003e\u003cpre\u003e\u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e].\u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n\u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e] = \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e);\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e最后判断除了\u003ccode\u003estaleSlot\u003c/code\u003e以外,还发现了其他过期的\u003ccode\u003eslot\u003c/code\u003e数据,就要开启清理数据的逻辑:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"if (slotToExpunge != staleSlot)\n cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e != \u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e)\n \u003cspan class=\"pl-en\"\u003ecleanSomeSlots\u003c/span\u003e(\u003cspan class=\"pl-en\"\u003eexpungeStaleEntry\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003eslotToExpunge\u003c/span\u003e), \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e);\u003c/pre\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocalMap\u003c/code\u003e过期 key 的探测式清理流程\u003c/h3\u003e\u003ca id=\"user-content-threadlocalmap过期-key-的探测式清理流程\" class=\"anchor\" aria-label=\"Permalink: ThreadLocalMap过期 key 的探测式清理流程\" href=\"#threadlocalmap过期-key-的探测式清理流程\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e上面我们有提及\u003ccode\u003eThreadLocalMap\u003c/code\u003e的两种过期\u003ccode\u003ekey\u003c/code\u003e数据清理方式:\u003cstrong\u003e探测式清理\u003c/strong\u003e和\u003cstrong\u003e启发式清理\u003c/strong\u003e。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e我们先讲下探测式清理,也就是\u003ccode\u003eexpungeStaleEntry\u003c/code\u003e方法,遍历散列数组,从开始位置向后探测清理过期数据,将过期数据的\u003ccode\u003eEntry\u003c/code\u003e设置为\u003ccode\u003enull\u003c/code\u003e,沿途中碰到未过期的数据则将此数据\u003ccode\u003erehash\u003c/code\u003e后重新在\u003ccode\u003etable\u003c/code\u003e数组中定位,如果定位的位置已经有了数据,则会将未过期的数据放到最靠近此位置的\u003ccode\u003eEntry=null\u003c/code\u003e的桶中,使\u003ccode\u003erehash\u003c/code\u003e后的\u003ccode\u003eEntry\u003c/code\u003e数据距离正确的桶的位置更近一些。操作逻辑如下:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/18.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/18.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e如上图,\u003ccode\u003eset(27)\u003c/code\u003e 经过 hash 计算后应该落到\u003ccode\u003eindex=4\u003c/code\u003e的桶中,由于\u003ccode\u003eindex=4\u003c/code\u003e桶已经有了数据,所以往后迭代最终数据放入到\u003ccode\u003eindex=7\u003c/code\u003e的桶中,放入后一段时间后\u003ccode\u003eindex=5\u003c/code\u003e中的\u003ccode\u003eEntry\u003c/code\u003e数据\u003ccode\u003ekey\u003c/code\u003e变为了\u003ccode\u003enull\u003c/code\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/19.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/19.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e如果再有其他数据\u003ccode\u003eset\u003c/code\u003e到\u003ccode\u003emap\u003c/code\u003e中,就会触发\u003cstrong\u003e探测式清理\u003c/strong\u003e操作。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e如上图,执行\u003cstrong\u003e探测式清理\u003c/strong\u003e后,\u003ccode\u003eindex=5\u003c/code\u003e的数据被清理掉,继续往后迭代,到\u003ccode\u003eindex=7\u003c/code\u003e的元素时,经过\u003ccode\u003erehash\u003c/code\u003e后发现该元素正确的\u003ccode\u003eindex=4\u003c/code\u003e,而此位置已经有了数据,往后查找离\u003ccode\u003eindex=4\u003c/code\u003e最近的\u003ccode\u003eEntry=null\u003c/code\u003e的节点(刚被探测式清理掉的数据:\u003ccode\u003eindex=5\u003c/code\u003e),找到后移动\u003ccode\u003eindex= 7\u003c/code\u003e的数据到\u003ccode\u003eindex=5\u003c/code\u003e中,此时桶的位置离正确的位置\u003ccode\u003eindex=4\u003c/code\u003e更近了。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e经过一轮探测式清理后,\u003ccode\u003ekey\u003c/code\u003e过期的数据会被清理掉,没过期的数据经过\u003ccode\u003erehash\u003c/code\u003e重定位后所处的桶位置理论上更接近\u003ccode\u003ei= key.hashCode \u0026amp; (tab.len - 1)\u003c/code\u003e的位置。这种优化会提高整个散列表查询性能。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e接着看下\u003ccode\u003eexpungeStaleEntry()\u003c/code\u003e具体流程,我们还是以先原理图后源码讲解的方式来一步步梳理:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/20.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/20.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e我们假设\u003ccode\u003eexpungeStaleEntry(3)\u003c/code\u003e 来调用此方法,如上图所示,我们可以看到\u003ccode\u003eThreadLocalMap\u003c/code\u003e中\u003ccode\u003etable\u003c/code\u003e的数据情况,接着执行清理操作:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/21.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/21.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e第一步是清空当前\u003ccode\u003estaleSlot\u003c/code\u003e位置的数据,\u003ccode\u003eindex=3\u003c/code\u003e位置的\u003ccode\u003eEntry\u003c/code\u003e变成了\u003ccode\u003enull\u003c/code\u003e。然后接着往后探测:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/22.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/22.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e执行完第二步后,index=4 的元素挪到 index=3 的槽位中。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e继续往后迭代检查,碰到正常数据,计算该数据位置是否偏移,如果被偏移,则重新计算\u003ccode\u003eslot\u003c/code\u003e位置,目的是让正常数据尽可能存放在正确位置或离正确位置更近的位置\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/23.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/23.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e在往后迭代的过程中碰到空的槽位,终止探测,这样一轮探测式清理工作就完成了,接着我们继续看看具体\u003cstrong\u003e实现源代码\u003c/strong\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"private int expungeStaleEntry(int staleSlot) {\n Entry[] tab = table;\n int len = tab.length;\n\n tab[staleSlot].value = null;\n tab[staleSlot] = null;\n size--;\n\n Entry e;\n int i;\n for (i = nextIndex(staleSlot, len);\n (e = tab[i]) != null;\n i = nextIndex(i, len)) {\n ThreadLocal\u0026lt;?\u0026gt; k = e.get();\n if (k == null) {\n e.value = null;\n tab[i] = null;\n size--;\n } else {\n int h = k.threadLocalHashCode \u0026amp; (len - 1);\n if (h != i) {\n tab[i] = null;\n\n while (tab[h] != null)\n h = nextIndex(h, len);\n tab[h] = e;\n }\n }\n }\n return i;\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-en\"\u003eexpungeStaleEntry\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e[] \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etable\u003c/span\u003e;\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003elength\u003c/span\u003e;\n\n \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e].\u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e] = \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003esize\u003c/span\u003e--;\n\n \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e;\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e;\n \u003cspan class=\"pl-k\"\u003efor\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003enextIndex\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003estaleSlot\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e);\n (\u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e]) != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003enextIndex\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e)) {\n \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;?\u0026gt; \u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e();\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e == \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e) {\n \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e] = \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003esize\u003c/span\u003e--;\n } \u003cspan class=\"pl-k\"\u003eelse\u003c/span\u003e {\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003ethreadLocalHashCode\u003c/span\u003e \u0026amp; (\u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e - \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e);\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e != \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e) {\n \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e] = \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n\n \u003cspan class=\"pl-k\"\u003ewhile\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e] != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e)\n \u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003enextIndex\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e);\n \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e] = \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e;\n }\n }\n }\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e;\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e这里我们还是以\u003ccode\u003estaleSlot=3\u003c/code\u003e 来做示例说明,首先是将\u003ccode\u003etab[staleSlot]\u003c/code\u003e槽位的数据清空,然后设置\u003ccode\u003esize--\u003c/code\u003e\n接着以\u003ccode\u003estaleSlot\u003c/code\u003e位置往后迭代,如果遇到\u003ccode\u003ek==null\u003c/code\u003e的过期数据,也是清空该槽位数据,然后\u003ccode\u003esize--\u003c/code\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"ThreadLocal\u0026lt;?\u0026gt; k = e.get();\n\nif (k == null) {\n e.value = null;\n tab[i] = null;\n size--;\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;?\u0026gt; \u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e();\n\n\u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e == \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e) {\n \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e] = \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003esize\u003c/span\u003e--;\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e如果\u003ccode\u003ekey\u003c/code\u003e没有过期,重新计算当前\u003ccode\u003ekey\u003c/code\u003e的下标位置是不是当前槽位下标位置,如果不是,那么说明产生了\u003ccode\u003ehash\u003c/code\u003e冲突,此时以新计算出来正确的槽位位置往后迭代,找到最近一个可以存放\u003ccode\u003eentry\u003c/code\u003e的位置。\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"int h = k.threadLocalHashCode \u0026amp; (len - 1);\nif (h != i) {\n tab[i] = null;\n\n while (tab[h] != null)\n h = nextIndex(h, len);\n\n tab[h] = e;\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003ethreadLocalHashCode\u003c/span\u003e \u0026amp; (\u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e - \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e);\n\u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e != \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e) {\n \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e] = \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n\n \u003cspan class=\"pl-k\"\u003ewhile\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e] != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e)\n \u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003enextIndex\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e);\n\n \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e] = \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e;\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e这里是处理正常的产生\u003ccode\u003eHash\u003c/code\u003e冲突的数据,经过迭代后,有过\u003ccode\u003eHash\u003c/code\u003e冲突数据的\u003ccode\u003eEntry\u003c/code\u003e位置会更靠近正确位置,这样的话,查询的时候 效率才会更高。\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocalMap\u003c/code\u003e扩容机制\u003c/h3\u003e\u003ca id=\"user-content-threadlocalmap扩容机制\" class=\"anchor\" aria-label=\"Permalink: ThreadLocalMap扩容机制\" href=\"#threadlocalmap扩容机制\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e在\u003ccode\u003eThreadLocalMap.set()\u003c/code\u003e方法的最后,如果执行完启发式清理工作后,未清理到任何数据,且当前散列数组中\u003ccode\u003eEntry\u003c/code\u003e的数量已经达到了列表的扩容阈值\u003ccode\u003e(len*2/3)\u003c/code\u003e,就开始执行\u003ccode\u003erehash()\u003c/code\u003e逻辑:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"if (!cleanSomeSlots(i, sz) \u0026amp;\u0026amp; sz \u0026gt;= threshold)\n rehash();\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (!\u003cspan class=\"pl-en\"\u003ecleanSomeSlots\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003esz\u003c/span\u003e) \u0026amp;\u0026amp; \u003cspan class=\"pl-s1\"\u003esz\u003c/span\u003e \u0026gt;= \u003cspan class=\"pl-s1\"\u003ethreshold\u003c/span\u003e)\n \u003cspan class=\"pl-en\"\u003erehash\u003c/span\u003e();\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e接着看下\u003ccode\u003erehash()\u003c/code\u003e具体实现:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"private void rehash() {\n expungeStaleEntries();\n\n if (size \u0026gt;= threshold - threshold / 4)\n resize();\n}\n\nprivate void expungeStaleEntries() {\n Entry[] tab = table;\n int len = tab.length;\n for (int j = 0; j \u0026lt; len; j++) {\n Entry e = tab[j];\n if (e != null \u0026amp;\u0026amp; e.get() == null)\n expungeStaleEntry(j);\n }\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003erehash\u003c/span\u003e() {\n \u003cspan class=\"pl-en\"\u003eexpungeStaleEntries\u003c/span\u003e();\n\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003esize\u003c/span\u003e \u0026gt;= \u003cspan class=\"pl-s1\"\u003ethreshold\u003c/span\u003e - \u003cspan class=\"pl-s1\"\u003ethreshold\u003c/span\u003e / \u003cspan class=\"pl-c1\"\u003e4\u003c/span\u003e)\n \u003cspan class=\"pl-en\"\u003eresize\u003c/span\u003e();\n}\n\n\u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003eexpungeStaleEntries\u003c/span\u003e() {\n \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e[] \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etable\u003c/span\u003e;\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003elength\u003c/span\u003e;\n \u003cspan class=\"pl-k\"\u003efor\u003c/span\u003e (\u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ej\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003e0\u003c/span\u003e; \u003cspan class=\"pl-s1\"\u003ej\u003c/span\u003e \u0026lt; \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e; \u003cspan class=\"pl-s1\"\u003ej\u003c/span\u003e++) {\n \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ej\u003c/span\u003e];\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e \u0026amp;\u0026amp; \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e() == \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e)\n \u003cspan class=\"pl-en\"\u003eexpungeStaleEntry\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ej\u003c/span\u003e);\n }\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e这里首先是会进行探测式清理工作,从\u003ccode\u003etable\u003c/code\u003e的起始位置往后清理,上面有分析清理的详细流程。清理完成之后,\u003ccode\u003etable\u003c/code\u003e中可能有一些\u003ccode\u003ekey\u003c/code\u003e为\u003ccode\u003enull\u003c/code\u003e的\u003ccode\u003eEntry\u003c/code\u003e数据被清理掉,所以此时通过判断\u003ccode\u003esize \u0026gt;= threshold - threshold / 4\u003c/code\u003e 也就是\u003ccode\u003esize \u0026gt;= threshold * 3/4\u003c/code\u003e 来决定是否扩容。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e我们还记得上面进行\u003ccode\u003erehash()\u003c/code\u003e的阈值是\u003ccode\u003esize \u0026gt;= threshold\u003c/code\u003e,所以当面试官套路我们\u003ccode\u003eThreadLocalMap\u003c/code\u003e扩容机制的时候 我们一定要说清楚这两个步骤:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/24.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/24.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e接着看看具体的\u003ccode\u003eresize()\u003c/code\u003e方法,为了方便演示,我们以\u003ccode\u003eoldTab.len=8\u003c/code\u003e来举例:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/25.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/25.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e扩容后的\u003ccode\u003etab\u003c/code\u003e的大小为\u003ccode\u003eoldLen * 2\u003c/code\u003e,然后遍历老的散列表,重新计算\u003ccode\u003ehash\u003c/code\u003e位置,然后放到新的\u003ccode\u003etab\u003c/code\u003e数组中,如果出现\u003ccode\u003ehash\u003c/code\u003e冲突则往后寻找最近的\u003ccode\u003eentry\u003c/code\u003e为\u003ccode\u003enull\u003c/code\u003e的槽位,遍历完成之后,\u003ccode\u003eoldTab\u003c/code\u003e中所有的\u003ccode\u003eentry\u003c/code\u003e数据都已经放入到新的\u003ccode\u003etab\u003c/code\u003e中了。重新计算\u003ccode\u003etab\u003c/code\u003e下次扩容的\u003cstrong\u003e阈值\u003c/strong\u003e,具体代码如下:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"private void resize() {\n Entry[] oldTab = table;\n int oldLen = oldTab.length;\n int newLen = oldLen * 2;\n Entry[] newTab = new Entry[newLen];\n int count = 0;\n\n for (int j = 0; j \u0026lt; oldLen; ++j) {\n Entry e = oldTab[j];\n if (e != null) {\n ThreadLocal\u0026lt;?\u0026gt; k = e.get();\n if (k == null) {\n e.value = null;\n } else {\n int h = k.threadLocalHashCode \u0026amp; (newLen - 1);\n while (newTab[h] != null)\n h = nextIndex(h, newLen);\n newTab[h] = e;\n count++;\n }\n }\n }\n\n setThreshold(newLen);\n size = count;\n table = newTab;\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003eresize\u003c/span\u003e() {\n \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e[] \u003cspan class=\"pl-s1\"\u003eoldTab\u003c/span\u003e = 8000 \u003cspan class=\"pl-s1\"\u003etable\u003c/span\u003e;\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eoldLen\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003eoldTab\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003elength\u003c/span\u003e;\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003enewLen\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003eoldLen\u003c/span\u003e * \u003cspan class=\"pl-c1\"\u003e2\u003c/span\u003e;\n \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e[] \u003cspan class=\"pl-s1\"\u003enewTab\u003c/span\u003e = \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003enewLen\u003c/span\u003e];\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ecount\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003e0\u003c/span\u003e;\n\n \u003cspan class=\"pl-k\"\u003efor\u003c/span\u003e (\u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ej\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003e0\u003c/span\u003e; \u003cspan class=\"pl-s1\"\u003ej\u003c/span\u003e \u0026lt; \u003cspan class=\"pl-s1\"\u003eoldLen\u003c/span\u003e; ++\u003cspan class=\"pl-s1\"\u003ej\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003eoldTab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ej\u003c/span\u003e];\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;?\u0026gt; \u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e();\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e == \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e) {\n \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003evalue\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n } \u003cspan class=\"pl-k\"\u003eelse\u003c/span\u003e {\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003ethreadLocalHashCode\u003c/span\u003e \u0026amp; (\u003cspan class=\"pl-s1\"\u003enewLen\u003c/span\u003e - \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e);\n \u003cspan class=\"pl-k\"\u003ewhile\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003enewTab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e] != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e)\n \u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003enextIndex\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003enewLen\u003c/span\u003e);\n \u003cspan class=\"pl-s1\"\u003enewTab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003eh\u003c/span\u003e] = \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003ecount\u003c/span\u003e++;\n }\n }\n }\n\n \u003cspan class=\"pl-en\"\u003esetThreshold\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003enewLen\u003c/span\u003e);\n \u003cspan class=\"pl-s1\"\u003esize\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ecount\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003etable\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003enewTab\u003c/span\u003e;\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocalMap.get()\u003c/code\u003e详解\u003c/h3\u003e\u003ca id=\"user-content-threadlocalmapget详解\" class=\"anchor\" aria-label=\"Permalink: ThreadLocalMap.get()详解\" href=\"#threadlocalmapget详解\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e上面已经看完了\u003ccode\u003eset()\u003c/code\u003e方法的源码,其中包括\u003ccode\u003eset\u003c/code\u003e数据、清理数据、优化数据桶的位置等操作,接着看看\u003ccode\u003eget()\u003c/code\u003e操作的原理。\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocalMap.get()\u003c/code\u003e图解\u003c/h4\u003e\u003ca id=\"user-content-threadlocalmapget图解\" class=\"anchor\" aria-label=\"Permalink: ThreadLocalMap.get()图解\" href=\"#threadlocalmapget图解\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003e第一种情况:\u003c/strong\u003e 通过查找\u003ccode\u003ekey\u003c/code\u003e值计算出散列表中\u003ccode\u003eslot\u003c/code\u003e位置,然后该\u003ccode\u003eslot\u003c/code\u003e位置中的\u003ccode\u003eEntry.key\u003c/code\u003e和查找的\u003ccode\u003ekey\u003c/code\u003e一致,则直接返回:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/26.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/26.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003e第二种情况:\u003c/strong\u003e \u003ccode\u003eslot\u003c/code\u003e位置中的\u003ccode\u003eEntry.key\u003c/code\u003e和要查找的\u003ccode\u003ekey\u003c/code\u003e不一致:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/27.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/27.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e我们以\u003ccode\u003eget(ThreadLocal1)\u003c/code\u003e为例,通过\u003ccode\u003ehash\u003c/code\u003e计算后,正确的\u003ccode\u003eslot\u003c/code\u003e位置应该是 4,而\u003ccode\u003eindex=4\u003c/code\u003e的槽位已经有了数据,且\u003ccode\u003ekey\u003c/code\u003e值不等于\u003ccode\u003eThreadLocal1\u003c/code\u003e,所以需要继续往后迭代查找。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e迭代到\u003ccode\u003eindex=5\u003c/code\u003e的数据时,此时\u003ccode\u003eEntry.key=null\u003c/code\u003e,触发一次探测式数据回收操作,执行\u003ccode\u003eexpungeStaleEntry()\u003c/code\u003e方法,执行完后,\u003ccode\u003eindex 5,8\u003c/code\u003e的数据都会被回收,而\u003ccode\u003eindex 6,7\u003c/code\u003e的数据都会前移,此时继续往后迭代,到\u003ccode\u003eindex = 6\u003c/code\u003e的时候即找到了\u003ccode\u003ekey\u003c/code\u003e值相等的\u003ccode\u003eEntry\u003c/code\u003e数据,如下图所示:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/28.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/28.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocalMap.get()\u003c/code\u003e源码详解\u003c/h4\u003e\u003ca id=\"user-content-threadlocalmapget源码详解\" class=\"anchor\" aria-label=\"Permalink: ThreadLocalMap.get()源码详解\" href=\"#threadlocalmapget源码详解\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003ccode\u003ejava.lang.ThreadLocal.ThreadLocalMap.getEntry()\u003c/code\u003e:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"private Entry getEntry(ThreadLocal\u0026lt;?\u0026gt; key) {\n int i = key.threadLocalHashCode \u0026amp; (table.length - 1);\n Entry e = table[i];\n if (e != null \u0026amp;\u0026amp; e.get() == key)\n return e;\n else\n return getEntryAfterMiss(key, i, e);\n}\n\nprivate Entry getEntryAfterMiss(ThreadLocal\u0026lt;?\u0026gt; key, int i, Entry e) {\n Entry[] tab = table;\n int len = tab.length;\n\n while (e != null) {\n ThreadLocal\u0026lt;?\u0026gt; k = e.get();\n if (k == key)\n return e;\n if (k == null)\n expungeStaleEntry(i);\n else\n i = nextIndex(i, len);\n e = tab[i];\n }\n return null;\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e \u003cspan class=\"pl-en\"\u003egetEntry\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;?\u0026gt; \u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003ethreadLocalHashCode\u003c/span\u003e \u0026amp; (\u003cspan class=\"pl-s1\"\u003etable\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003elength\u003c/span\u003e - \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e);\n \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etable\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e];\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e \u0026amp;\u0026amp; \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e() == \u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e)\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e;\n \u003cspan class=\"pl-k\"\u003eelse\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-en\"\u003egetEntryAfterMiss\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e);\n}\n\n\u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e \u003cspan class=\"pl-en\"\u003egetEntryAfterMiss\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;?\u0026gt; \u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e[] \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etable\u003c/span\u003e;\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003elength\u003c/span\u003e;\n\n \u003cspan class=\"pl-k\"\u003ewhile\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;?\u0026gt; \u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e();\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e == \u003cspan class=\"pl-s1\"\u003ekey\u003c/span\u003e)\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e;\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ek\u003c/span\u003e == \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e)\n \u003cspan class=\"pl-en\"\u003eexpungeStaleEntry\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e);\n \u003cspan class=\"pl-k\"\u003eelse\u003c/span\u003e\n \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003enextIndex\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e);\n \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e];\n }\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e;\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocalMap\u003c/code\u003e过期 key 的启发式清理流程\u003c/h3\u003e\u003ca id=\"user-content-threadlocalmap过期-key-的启发式清理流程\" class=\"anchor\" aria-label=\"Permalink: ThreadLocalMap过期 key 的启发式清理流程\" href=\"#threadlocalmap过期-key-的启发式清理流程\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e上面多次提及到\u003ccode\u003eThreadLocalMap\u003c/code\u003e过期key的两种清理方式:\u003cstrong\u003e探测式清理(expungeStaleEntry())\u003c/strong\u003e、\u003cstrong\u003e启发式清理(cleanSomeSlots())\u003c/strong\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e探测式清理是以当前\u003ccode\u003eEntry\u003c/code\u003e 往后清理,遇到值为\u003ccode\u003enull\u003c/code\u003e则结束清理,属于\u003cstrong\u003e线性探测清理\u003c/strong\u003e。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e而启发式清理被作者定义为:\u003cstrong\u003eHeuristically scan some cells looking for stale entries\u003c/strong\u003e.\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/29.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/29.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e具体代码如下:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"private boolean cleanSomeSlots(int i, int n) {\n boolean removed = false;\n Entry[] tab = table;\n int len = tab.length;\n do {\n i = nextIndex(i, len);\n Entry e = tab[i];\n if (e != null \u0026amp;\u0026amp; e.get() == null) {\n n = len;\n removed = true;\n i = expungeStaleEntry(i);\n }\n } while ( (n \u0026gt;\u0026gt;\u0026gt;= 1) != 0);\n return removed;\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eboolean\u003c/span\u003e \u003cspan class=\"pl-en\"\u003ecleanSomeSlots\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003en\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eboolean\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eremoved\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003efalse\u003c/span\u003e;\n \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e[] \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etable\u003c/span\u003e;\n \u003cspan class=\"pl-smi\"\u003eint\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003elength\u003c/span\u003e;\n \u003cspan class=\"pl-k\"\u003edo\u003c/span\u003e {\n \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003enextIndex\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e);\n \u003cspan class=\"pl-smi\"\u003eEntry\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003etab\u003c/span\u003e[\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e];\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e \u0026amp;\u0026amp; \u003cspan class=\"pl-s1\"\u003ee\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e() == \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e) {\n \u003cspan class=\"pl-s1\"\u003en\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003elen\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003eremoved\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003etrue\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003eexpungeStaleEntry\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003ei\u003c/span\u003e);\n }\n } \u003cspan class=\"pl-k\"\u003ewhile\u003c/span\u003e ( (\u003cspan class=\"pl-s1\"\u003en\u003c/span\u003e \u0026gt;\u0026gt;\u0026gt;= \u003cspan class=\"pl-c1\"\u003e1\u003c/span\u003e) != \u003cspan class=\"pl-c1\"\u003e0\u003c/span\u003e);\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eremoved\u003c/span\u003e;\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eInheritableThreadLocal\u003c/code\u003e\u003c/h3\u003e\u003ca id=\"user-content-inheritablethreadlocal\" class=\"anchor\" aria-label=\"Permalink: InheritableThreadLocal\" href=\"#inheritablethreadlocal\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e我们使用\u003ccode\u003eThreadLocal\u003c/code\u003e的时候,在异步场景下是无法给子线程共享父线程中创建的线程副本数据的。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e为了解决这个问题,JDK 中还有一个\u003ccode\u003eInheritableThreadLocal\u003c/code\u003e类,我们来看一个例子:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"public class InheritableThreadLocalDemo {\n public static void main(String[] args) {\n ThreadLocal\u0026lt;String\u0026gt; ThreadLocal = new ThreadLocal\u0026lt;\u0026gt;();\n ThreadLocal\u0026lt;String\u0026gt; inheritableThreadLocal = new InheritableThreadLocal\u0026lt;\u0026gt;();\n ThreadLocal.set(\u0026quot;父类数据:threadLocal\u0026quot;);\n inheritableThreadLocal.set(\u0026quot;父类数据:inheritableThreadLocal\u0026quot;);\n\n new Thread(new Runnable() {\n @Override\n public void run() {\n System.out.println(\u0026quot;子线程获取父类ThreadLocal数据:\u0026quot; + ThreadLocal.get());\n System.out.println(\u0026quot;子线程获取父类inheritableThreadLocal数据:\u0026quot; + inheritableThreadLocal.get());\n }\n }).start();\n }\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eclass\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eInheritableThreadLocalDemo\u003c/span\u003e {\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003estatic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003emain\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e[] \u003cspan class=\"pl-s1\"\u003eargs\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;\u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e\u0026gt; \u003cspan class=\"pl-s1\"\u003eThreadLocal\u003c/span\u003e = \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;\u0026gt;();\n \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e\u0026lt;\u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e\u0026gt; \u003cspan class=\"pl-s1\"\u003einheritableThreadLocal\u003c/span\u003e = \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eInheritableThreadLocal\u003c/span\u003e\u0026lt;\u0026gt;();\n \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eset\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"父类数据:threadLocal\"\u003c/span\u003e);\n \u003cspan class=\"pl-s1\"\u003einheritableThreadLocal\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eset\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"父类数据:inheritableThreadLocal\"\u003c/span\u003e);\n\n \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eThread\u003c/span\u003e(\u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eRunnable\u003c/span\u003e() {\n \u003cspan class=\"pl-c1\"\u003e@\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003eOverride\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003erun\u003c/span\u003e() {\n \u003cspan class=\"pl-smi\"\u003eSystem\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003eout\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eprintln\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"子线程获取父类ThreadLocal数据:\"\u003c/span\u003e + \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e());\n \u003cspan class=\"pl-smi\"\u003eSystem\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003eout\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eprintln\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"子线程获取父类inheritableThreadLocal数据:\"\u003c/span\u003e + \u003cspan class=\"pl-s1\"\u003einheritableThreadLocal\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e());\n }\n }).\u003cspan class=\"pl-en\"\u003estart\u003c/span\u003e();\n }\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e打印结果:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"子线程获取父类ThreadLocal数据:null\n子线程获取父类inheritableThreadLocal数据:父类数据:inheritableThreadLocal\"\u003e\u003cpre\u003e\u003cspan class=\"pl-smi\"\u003e子线程获取父类ThreadLocal数据\u003c/span\u003e:\u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e\n\u003cspan class=\"pl-s1\"\u003e子线程获取父类inheritableThreadLocal数据\u003c/span\u003e:\u003cspan class=\"pl-s1\"\u003e父类数据\u003c/span\u003e:\u003cspan class=\"pl-s1\"\u003einheritableThreadLocal\u003c/span\u003e\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e实现原理是子线程是通过在父线程中通过调用\u003ccode\u003enew Thread()\u003c/code\u003e方法来创建子线程,\u003ccode\u003eThread#init\u003c/code\u003e方法在\u003ccode\u003eThread\u003c/code\u003e的构造方法中被调用。在\u003ccode\u003einit\u003c/code\u003e方法中拷贝父线程数据到子线程中:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"private void init(ThreadGroup g, Runnable target, String name,\n long stackSize, AccessControlContext acc,\n boolean inheritThreadLocals) {\n if (name == null) {\n throw new NullPointerException(\u0026quot;name cannot be null\u0026quot;);\n }\n\n if (inheritThreadLocals \u0026amp;\u0026amp; parent.inheritableThreadLocals != null)\n this.inheritableThreadLocals =\n ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);\n this.stackSize = stackSize;\n tid = nextThreadID();\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003einit\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eThreadGroup\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eg\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eRunnable\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003etarget\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ename\u003c/span\u003e,\n \u003cspan class=\"pl-smi\"\u003elong\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003estackSize\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eAccessControlContext\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eacc\u003c/span\u003e,\n \u003cspan class=\"pl-smi\"\u003eboolean\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003einheritThreadLocals\u003c/span\u003e) {\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003ename\u003c/span\u003e == \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e) {\n \u003cspan class=\"pl-k\"\u003ethrow\u003c/span\u003e \u003cspan class=\"pl-k\"\u003enew\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eNullPointerException\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"name cannot be null\"\u003c/span\u003e);\n }\n\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003einheritThreadLocals\u003c/span\u003e \u0026amp;\u0026amp; \u003cspan class=\"pl-s1\"\u003eparent\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003einheritableThreadLocals\u003c/span\u003e != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e)\n \u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003einheritableThreadLocals\u003c/span\u003e =\n \u003cspan class=\"pl-smi\"\u003eThreadLocal\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003ecreateInheritedMap\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003eparent\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003einheritableThreadLocals\u003c/span\u003e);\n \u003cspan class=\"pl-smi\"\u003ethis\u003c/span\u003e.\u003cspan class=\"pl-s1\"\u003estackSize\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003estackSize\u003c/span\u003e;\n \u003cspan class=\"pl-s1\"\u003etid\u003c/span\u003e = \u003cspan class=\"pl-en\"\u003enextThreadID\u003c/span\u003e();\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e但\u003ccode\u003eInheritableThreadLocal\u003c/code\u003e仍然有缺陷,一般我们做异步化处理都是使用的线程池,而\u003ccode\u003eInheritableThreadLocal\u003c/code\u003e是在\u003ccode\u003enew Thread\u003c/code\u003e中的\u003ccode\u003einit()\u003c/code\u003e方法给赋值的,而线程池是线程复用的逻辑,所以这里会存在问题。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e当然,有问题出现就会有解决问题的方案,阿里巴巴开源了一个\u003ccode\u003eTransmittableThreadLocal\u003c/code\u003e组件就可以解决这个问题,这里就不再延伸,感兴趣的可自行查阅资料。\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch3 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocal\u003c/code\u003e项目中使用实战\u003c/h3\u003e\u003ca id=\"user-content-threadlocal项目中使用实战\" class=\"anchor\" aria-label=\"Permalink: ThreadLocal项目中使用实战\" href=\"#threadlocal项目中使用实战\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64 7B8D a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e\u003ccode\u003eThreadLocal\u003c/code\u003e使用场景\u003c/h4\u003e\u003ca id=\"user-content-threadlocal使用场景\" class=\"anchor\" aria-label=\"Permalink: ThreadLocal使用场景\" href=\"#threadlocal使用场景\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e我们现在项目中日志记录用的是\u003ccode\u003eELK+Logstash\u003c/code\u003e,最后在\u003ccode\u003eKibana\u003c/code\u003e中进行展示和检索。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e现在都是分布式系统统一对外提供服务,项目间调用的关系可以通过 \u003ccode\u003etraceId\u003c/code\u003e 来关联,但是不同项目之间如何传递 \u003ccode\u003etraceId\u003c/code\u003e 呢?\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e这里我们使用 \u003ccode\u003eorg.slf4j.MDC\u003c/code\u003e 来实现此功能,内部就是通过 \u003ccode\u003eThreadLocal\u003c/code\u003e 来实现的,具体实现如下:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e当前端发送请求到\u003cstrong\u003e服务 A\u003c/strong\u003e时,\u003cstrong\u003e服务 A\u003c/strong\u003e会生成一个类似\u003ccode\u003eUUID\u003c/code\u003e的\u003ccode\u003etraceId\u003c/code\u003e字符串,将此字符串放入当前线程的\u003ccode\u003eThreadLocal\u003c/code\u003e中,在调用\u003cstrong\u003e服务 B\u003c/strong\u003e的时候,将\u003ccode\u003etraceId\u003c/code\u003e写入到请求的\u003ccode\u003eHeader\u003c/code\u003e中,\u003cstrong\u003e服务 B\u003c/strong\u003e在接收请求时会先判断请求的\u003ccode\u003eHeader\u003c/code\u003e中是否有\u003ccode\u003etraceId\u003c/code\u003e,如果存在则写入自己线程的\u003ccode\u003eThreadLocal\u003c/code\u003e中。\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/30.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/30.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e图中的\u003ccode\u003erequestId\u003c/code\u003e即为我们各个系统链路关联的\u003ccode\u003etraceId\u003c/code\u003e,系统间互相调用,通过这个\u003ccode\u003erequestId\u003c/code\u003e即可找到对应链路,这里还有会有一些其他场景:\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e\u003ca target=\"_blank\" rel=\"noopener noreferrer\" href=\"/Ezi1h/Java_Guide/blob/master/docs/java/concurrent/images/thread-local/31.png\"\u003e\u003cimg src=\"/Ezi1h/Java_Guide/raw/master/docs/java/concurrent/images/thread-local/31.png\" alt=\"\" style=\"max-width: 100%;\"\u003e\u003c/a\u003e\u003c/p\u003e\n\u003cp dir=\"auto\"\u003e针对于这些场景,我们都可以有相应的解决方案,如下所示\u003c/p\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003eFeign 远程调用解决方案\u003c/h4\u003e\u003ca id=\"user-content-feign-远程调用解决方案\" class=\"anchor\" aria-label=\"Permalink: Feign 远程调用解决方案\" href=\"#feign-远程调用解决方案\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003e服务发送请求:\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"@Component\n@Slf4j\npublic class FeignInvokeInterceptor implements RequestInterceptor {\n\n @Override\n public void apply(RequestTemplate template) {\n String requestId = MDC.get(\u0026quot;requestId\u0026quot;);\n if (StringUtils.isNotBlank(requestId)) {\n template.header(\u0026quot;requestId\u0026quot;, requestId);\n }\n }\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-c1\"\u003e@\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003eComponent\u003c/span\u003e\n\u003cspan class=\"pl-c1\"\u003e@\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003eSlf4j\u003c/span\u003e\n\u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eclass\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eFeignInvokeInterceptor\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eimplements\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eRequestInterceptor\u003c/span\u003e {\n\n \u003cspan class=\"pl-c1\"\u003e@\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003eOverride\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003eapply\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eRequestTemplate\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003etemplate\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003erequestId\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003eMDC\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eget\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"requestId\"\u003c/span\u003e);\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-smi\"\u003eStringUtils\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eisNotBlank\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003erequestId\u003c/span\u003e)) {\n \u003cspan class=\"pl-s1\"\u003etemplate\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eheader\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"requestId\"\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003erequestId\u003c/span\u003e);\n }\n }\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e\u003cstrong\u003e服务接收请求:\u003c/strong\u003e\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"@Slf4j\n@Component\npublic class LogInterceptor extends HandlerInterceptorAdapter {\n\n @Override\n public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3) {\n MDC.remove(\u0026quot;requestId\u0026quot;);\n }\n\n @Override\n public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3) {\n }\n\n @Override\n public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {\n\n String requestId = request.getHeader(BaseConstant.REQUEST_ID_KEY);\n if (StringUtils.isBlank(requestId)) {\n requestId = UUID.randomUUID().toString().replace(\u0026quot;-\u0026quot;, \u0026quot;\u0026quot;);\n }\n MDC.put(\u0026quot;requestId\u0026quot;, requestId);\n return true;\n }\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-c1\"\u003e@\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003eSlf4j\u003c/span\u003e\n\u003cspan class=\"pl-c1\"\u003e@\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003eComponent\u003c/span\u003e\n\u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eclass\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eLogInterceptor\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eextends\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eHandlerInterceptorAdapter\u003c/span\u003e {\n\n \u003cspan class=\"pl-c1\"\u003e@\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003eOverride\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003eafterCompletion\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eHttpServletRequest\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003earg0\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eHttpServletResponse\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003earg1\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eObject\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003earg2\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eException\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003earg3\u003c/span\u003e) {\n \u003cspan class=\"pl-c1\"\u003eMDC\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eremove\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"requestId\"\u003c/span\u003e);\n }\n\n \u003cspan class=\"pl-c1\"\u003e@\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003eOverride\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003epostHandle\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eHttpServletRequest\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003earg0\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eHttpServletResponse\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003earg1\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eObject\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003earg2\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eModelAndView\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003earg3\u003c/span\u003e) {\n }\n\n \u003cspan class=\"pl-c1\"\u003e@\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003eOverride\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eboolean\u003c/span\u003e \u003cspan class=\"pl-en\"\u003epreHandle\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eHttpServletRequest\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003erequest\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eHttpServletResponse\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003eresponse\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eObject\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003ehandler\u003c/span\u003e) \u003cspan class=\"pl-k\"\u003ethrows\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eException\u003c/span\u003e {\n\n \u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003erequestId\u003c/span\u003e = \u003cspan class=\"pl-s1\"\u003erequest\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003egetHeader\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eBaseConstant\u003c/span\u003e.\u003cspan class=\"pl-c1\"\u003eREQUEST_ID_KEY\u003c/span\u003e);\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-smi\"\u003eStringUtils\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eisBlank\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003erequestId\u003c/span\u003e)) {\n \u003cspan class=\"pl-s1\"\u003erequestId\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003eUUID\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003erandomUUID\u003c/span\u003e().\u003cspan class=\"pl-en\"\u003etoString\u003c/span\u003e().\u003cspan class=\"pl-en\"\u003ereplace\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"-\"\u003c/span\u003e, \u003cspan class=\"pl-s\"\u003e\"\"\u003c/span\u003e);\n }\n \u003cspan class=\"pl-c1\"\u003eMDC\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eput\u003c/span\u003e(\u003cspan class=\"pl-s\"\u003e\"requestId\"\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003erequestId\u003c/span\u003e);\n \u003cspan class=\"pl-k\"\u003ereturn\u003c/span\u003e \u003cspan class=\"pl-c1\"\u003etrue\u003c/span\u003e;\n }\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e线程池异步调用,requestId 传递\u003c/h4\u003e\u003ca id=\"user-content-线程池异步调用requestid-传递\" class=\"anchor\" aria-label=\"Permalink: 线程池异步调用,requestId 传递\" href=\"#线程池异步调用requestid-传递\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e因为\u003ccode\u003eMDC\u003c/code\u003e是基于\u003ccode\u003eThreadLocal\u003c/code\u003e去实现的,异步过程中,子线程并没有办法获取到父线程\u003ccode\u003eThreadLocal\u003c/code\u003e存储的数据,所以这里可以自定义线程池执行器,修改其中的\u003ccode\u003erun()\u003c/code\u003e方法:\u003c/p\u003e\n\u003cdiv class=\"highlight highlight-source-java notranslate position-relative overflow-auto\" dir=\"auto\" data-snippet-clipboard-copy-content=\"public class MyThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {\n\n @Override\n public void execute(Runnable runnable) {\n Map\u0026lt;String, String\u0026gt; context = MDC.getCopyOfContextMap();\n super.execute(() -\u0026gt; run(runnable, context));\n }\n\n @Override\n private void run(Runnable runnable, Map\u0026lt;String, String\u0026gt; context) {\n if (context != null) {\n MDC.setContextMap(context);\n }\n try {\n runnable.run();\n } finally {\n MDC.remove();\n }\n }\n}\"\u003e\u003cpre\u003e\u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eclass\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eMyThreadPoolTaskExecutor\u003c/span\u003e \u003cspan class=\"pl-k\"\u003eextends\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003eThreadPoolTaskExecutor\u003c/span\u003e {\n\n \u003cspan class=\"pl-c1\"\u003e@\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003eOverride\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003epublic\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003eexecute\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eRunnable\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003erunnable\u003c/span\u003e) {\n \u003cspan class=\"pl-smi\"\u003eMap\u003c/span\u003e\u0026lt;\u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e\u0026gt; \u003cspan class=\"pl-s1\"\u003econtext\u003c/span\u003e = \u003cspan class=\"pl-c1\"\u003eMDC\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003egetCopyOfContextMap\u003c/span\u003e();\n \u003cspan class=\"pl-en\"\u003esuper\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eexecute\u003c/span\u003e(() -\u0026gt; \u003cspan class=\"pl-en\"\u003erun\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003erunnable\u003c/span\u003e, \u003cspan class=\"pl-s1\"\u003econtext\u003c/span\u003e));\n }\n\n \u003cspan class=\"pl-c1\"\u003e@\u003c/span\u003e\u003cspan class=\"pl-c1\"\u003eOverride\u003c/span\u003e\n \u003cspan class=\"pl-k\"\u003eprivate\u003c/span\u003e \u003cspan class=\"pl-smi\"\u003evoid\u003c/span\u003e \u003cspan class=\"pl-en\"\u003erun\u003c/span\u003e(\u003cspan class=\"pl-smi\"\u003eRunnable\u003c/span\u003e \u003cspan class=\"pl-s1\"\u003erunnable\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eMap\u003c/span\u003e\u0026lt;\u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e, \u003cspan class=\"pl-smi\"\u003eString\u003c/span\u003e\u0026gt; \u003cspan class=\"pl-s1\"\u003econtext\u003c/span\u003e) {\n \u003cspan class=\"pl-k\"\u003eif\u003c/span\u003e (\u003cspan class=\"pl-s1\"\u003econtext\u003c/span\u003e != \u003cspan class=\"pl-c1\"\u003enull\u003c/span\u003e) {\n \u003cspan class=\"pl-c1\"\u003eMDC\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003esetContextMap\u003c/span\u003e(\u003cspan class=\"pl-s1\"\u003econtext\u003c/span\u003e);\n }\n \u003cspan class=\"pl-k\"\u003etry\u003c/span\u003e {\n \u003cspan class=\"pl-s1\"\u003erunnable\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003erun\u003c/span\u003e();\n } \u003cspan class=\"pl-k\"\u003efinally\u003c/span\u003e {\n \u003cspan class=\"pl-c1\"\u003eMDC\u003c/span\u003e.\u003cspan class=\"pl-en\"\u003eremove\u003c/span\u003e();\n }\n }\n}\u003c/pre\u003e\u003c/div\u003e\n\u003cdiv class=\"markdown-heading\" dir=\"auto\"\u003e\u003ch4 tabindex=\"-1\" class=\"heading-element\" dir=\"auto\"\u003e使用 MQ 发送消息给第三方系统\u003c/h4\u003e\u003ca id=\"user-content-使用-mq-发送消息给第三方系统\" class=\"anchor\" aria-label=\"Permalink: 使用 MQ 发送消息给第三方系统\" href=\"#使用-mq-发送消息给第三方系统\"\u003e\u003csvg class=\"octicon octicon-link\" viewBox=\"0 0 16 16\" version=\"1.1\" width=\"16\" height=\"16\" aria-hidden=\"true\"\u003e\u003cpath d=\"m7.775 3.275 1.25-1.25a3.5 3.5 0 1 1 4.95 4.95l-2.5 2.5a3.5 3.5 0 0 1-4.95 0 .751.751 0 0 1 .018-1.042.751.751 0 0 1 1.042-.018 1.998 1.998 0 0 0 2.83 0l2.5-2.5a2.002 2.002 0 0 0-2.83-2.83l-1.25 1.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042Zm-4.69 9.64a1.998 1.998 0 0 0 2.83 0l1.25-1.25a.751.751 0 0 1 1.042.018.751.751 0 0 1 .018 1.042l-1.25 1.25a3.5 3.5 0 1 1-4.95-4.95l2.5-2.5a3.5 3.5 0 0 1 4.95 0 .751.751 0 0 1-.018 1.042.751.751 0 0 1-1.042.018 1.998 1.998 0 0 0-2.83 0l-2.5 2.5a1.998 1.998 0 0 0 0 2.83Z\"\u003e\u003c/path\u003e\u003c/svg\u003e\u003c/a\u003e\u003c/div\u003e\n\u003cp dir=\"auto\"\u003e在 MQ 发送的消息体中自定义属性\u003ccode\u003erequestId\u003c/code\u003e,接收方消费消息后,自己解析\u003ccode\u003erequestId\u003c/code\u003e使用即可。\u003c/p\u003e\n\u003c/article\u003e","renderedFileInfo":null,"shortPath":null,"symbolsEnabled":true,"tabSize":8,"topBannersInfo":{"overridingGlobalFundingFile":false,"globalPreferredFundingPath":null,"showInvalidCitationWarning":false,"citationHelpUrl":"https://docs.github.com/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-citation-files","actionsOnboardingTip":null},"truncated":false,"viewable":true,"workflowRedirectUrl":null,"symbols":null},"copilotInfo":null,"copilotAccessAllowed":false,"modelsAccessAllowed":false,"modelsRepoIntegrationEnabled":false,"csrf_tokens":{"/Ezi1h/Java_Guide/branches":{"post":"iRtlWiQeQR_hcxtQNFXe95uB3BjZpgZyBCbkbixK8j0u_iHriyVBTY46kYTfQSwBL6nOs2cNuLWOhFWbNuYRWQ"},"/repos/preferences":{"post":"rh9DsELj9ucALCruueD4vH4OvrGVOi1tuWZ5ldh2ehmxBWmUWlMVQfYwZNKEDoushb3gQ2up22NIZm4AgfoRQA"}}},"title":"Java_Guide/docs/java/concurrent/threadlocal.md at master · Ezi1h/Java_Guide","appPayload":{"helpUrl":"https://docs.github.com","findFileWorkerPath":"/assets-cdn/worker/find-file-worker-263cab1760dd.js","findInFileWorkerPath":"/assets-cdn/worker/find-in-file-worker-1b17b3e7786a.js","githubDevUrl":null,"enabled_features":{"code_nav_ui_events":false,"react_blob_overlay":false,"accessible_code_button":true}}}
0