当前位置: 首页 > news >正文

ThreadLocal原理解析及面试

基本使用

讲原理之前,我简单写个demo小程序说说怎么使用

public class TestThreadLocal {public static void main(String[] args) throws InterruptedException {ThreadLocal<String> tl  = new ThreadLocal();/**主线程设置了一个值*/tl.set("SSSSSs");//tl.set(new Integer(2));InheritableThreadLocal<String> itl = new InheritableThreadLocal<>();itl.set("itlValue");new Thread(new Runnable() {@Overridepublic void run() {/**子线程没有设置,所以肯定拿不到*/System.out.println("tl的值:"+Thread.currentThread().getName()+tl.get());/**InheritableThreadLocal是可以传递到子线程的,所以这里可以拿到*/System.out.println("Itl的值:"+ Thread.currentThread().getName() + itl.get());}}).start();new Thread(new Runnable() {@Overridepublic void run() {tl.set("ttttt");/**这个子线程设置了,是可以拿到*/System.out.println("tl的值:"+Thread.currentThread().getName()+tl.get());/**InheritableThreadLocal是可以传递到子线程的,所以这里可以拿到*/System.out.println("Itl的值:"+ Thread.currentThread().getName() + itl.get());/**这里我们改一下看看主线程里的会不会修改*/itl.set("itl's Value changed By thread2");System.out.println("Itl的值:"+ Thread.currentThread().getName() + itl.get());}}).start();Thread.sleep(2000);System.out.println(Thread.currentThread().getName()+tl.get());/**这里我们可以看到子线程里修改InheritableThreadLocal的值是不会影响主线程的*/System.out.println("Itl的值:"+ Thread.currentThread().getName() + itl.get());}
}

运行结果 

tl的值:Thread-0null
Itl的值:Thread-0itlValue
tl的值:Thread-1ttttt
Itl的值:Thread-1itlValue
Itl的值:Thread-1itl's Value changed By thread2
mainSSSSSs
Itl的值:mainitlValue

我们可以看到只有某个线程设置了 ThreadLocal的值才能取到,不设置是没有的,这就实现了线程间的隔离。另外对于InheritableThreadLocal它会继承父线程的 InheritableThreadLocal 变量的值(实际上是值的引用副本),所以修改对于主线程和其他子线程是无效的,但是对自己有效,个人觉得这很变态,不管了,这不是重点。

Thread和ThreadLocal的关系

ThreadLocal 和 Thread 是 Java 中处理多线程编程时的两个重要概念,它们在处理线程本地变量时有着不同的角色和用途。

Thread

Thread 是 Java 中用于表示线程的对象。每个 Thread 对象代表一个独立的执行路径,可以在并发环境中同时运行。Java 的多线程编程模型允许你创建多个 Thread 对象,以并行或并发的方式执行多个任务。

Thread 类提供了许多方法来管理和控制线程的行为,比如:

  • start(): 启动线程。
  • run(): 线程的主体方法,包含线程要执行的任务代码。
  • sleep(long millis): 使当前线程休眠指定的毫秒数。
  • interrupt(): 中断线程。
  • join(): 等待另一个线程终止。
  • isAlive(): 检查线程是否还在运行。

ThreadLocal

ThreadLocal 是 Java 提供的一个工具类,用于创建线程局部变量。这些变量在每个线程中都有独立的初始值和副本,因此每个线程都可以独立地修改自己的变量副本,而不会影响到其他线程的副本。

ThreadLocal 提供了一种将变量与线程绑定的机制,这样每个线程都可以访问到属于自己的、独立的数据副本。这在多线程编程中特别有用,特别是在需要确保线程间数据隔离的场景中。

ThreadLocal 的主要方法包括:

  • get(): 获取当前线程所对应的值。
  • set(T value): 设置当前线程的值。
  • initialValue(): 提供线程局部变量的初始值,该方法是一个受保护的方法,通常需要在子类中重写。
  • remove(): 移除当前线程的值。

ThreadLocal变量是Thread私有的,一个Thread里设置的值其他的Thread看不到(即使是子线程也不能看到,如果想要子线程看到,可以使用InheritableThreadLocal,这个也曾经被面试过),本文主要讲解ThreadLocal,也会把InheritableThreadLocal顺便讲一下,但是不会当成重点,毕竟面试的时候也不是重点。

下面是Thread类的定义的一部分

public
class Thread implements Runnable {/* Make sure registerNatives is the first thing <clinit> does. */private static native void registerNatives();static {registerNatives();}private volatile String name;private int            priority;private Thread         threadQ;private long           eetop;/* Whether or not to single_step this thread. */private boolean     single_step;/* Whether or not the thread is a daemon thread. */private boolean     daemon = false;/* JVM state */private boolean     stillborn = false;/* What will be run. */private Runnable target;/* The group of this thread */private ThreadGroup group;/* The context ClassLoader for this thread */private ClassLoader contextClassLoader;/* The inherited AccessControlContext of this thread */private AccessControlContext inheritedAccessControlContext;/* For autonumbering anonymous threads. */private static int threadInitNumber;private static synchronized int nextThreadNum() {return threadInitNumber++;}/* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. 注意看这个变量,这就是我们的ThreadLocal变量的存放位置,按照它的注释,这个Map是存放在ThreadLocal里的*/ThreadLocal.ThreadLocalMap threadLocals = null;/** InheritableThreadLocal values pertaining to this thread. This map is* maintained by the InheritableThreadLocal class.*/ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

    咱们怎么去理解呢,我们可以理解为这是一个 ThreadLocal.ThreadLocalMap类型的变量,它是在Thread里存放的,类型是 ThreadLocal.ThreadLocalMap,这个大家想想就能理解,如果不存放在Thread里怎么能实现线程自己用自己的呢。

ThreadLocal类里关于ThreadLocalMap的定义(每个Thread里都有一个ThreadLocalMap),可以说ThreadLocal关键是ThreadLocalMap,它自己只是定义了对外的接口,其他的逻辑都在ThreadLocalMap里(map的key是ThreadLocal的引用,value是我们在当前线程中设置的值),ThreadLocalMap里重点需要关注Entry,它继承了WeakReference,其实我们设置的值就是设置到这个弱引用的对象的value里,而弱引用只要垃圾回收,就会被回收掉

网上借了一张图用来解释这些对象的关系

图的组成

  1. 左侧流程图
    • 初始化ThreadLocal对象:首先,通过ThreadLocal的构造函数创建一个空的ThreadLocal对象。这个对象本身并不存储值,而是作为一个容器,用于在线程中存储和检索值。
    • 设置值:接着,通过调用ThreadLocal对象的set方法,可以为当前线程设置一个值。这个值是与当前线程相关联的,对其他线程不可见。
  2. 右侧结构图
    • 线程局部变量结构:右侧展示了ThreadLocal内部如何存储线程局部变量的结构。每个线程都有一个与之关联的ThreadLocalMap,这个映射表存储了键(ThreadLocal对象)和值(线程局部变量)的对应关系。
    • 键(key):这里的键是ThreadLocal对象本身。
    • 值(value):与键相关联的值,即线程局部变量。
    • 弱引用(weakReference)ThreadLocalMap中的键(即ThreadLocal对象)是通过弱引用持有的。这意味着如果ThreadLocal对象没有其他强引用,它将被垃圾回收器回收,即使它仍然作为键存在于映射表中。
  3. 注释
    • 注释部分解释了为什么ThreadLocalMap中的键要使用弱引用而不是强引用。如果使用强引用,即使ThreadLocal对象本身(即键)被设置为null(或不再被引用),它仍然会作为键存在于映射表中,导致内存泄漏。因为强引用会阻止垃圾回收器回收不再使用的对象。而使用弱引用,当ThreadLocal对象没有其他强引用时,它可以被垃圾回收器回收,从而避免内存泄漏。

总结

这张图通过流程图和结构图相结合的方式,清晰地展示了ThreadLocal的工作原理以及为什么在其内部结构中使用了弱引用来避免内存泄漏。它强调了ThreadLocal在Java多线程编程中的重要性,以及如何使用它来管理线程内的共享数据,同时保持数据的线程隔离性。重点记住ThreadLocal有两个引用,一个是强引用的tl(只要引用在,即使OOM都不会被回收),另一个是弱引用的map里的key(只要发生GC就会被回收)

为什么要这么设计以及内存泄漏问题

为什么Entry要使用弱引用(key可能导致内存泄漏):

如果是强引用,即使程序中把tl设置为null,但是当前情况是两个强引用指向ThreadLocal这块内存,把tl设置为null只是去掉了一个强引用,还剩下一个强引用,所以依然无法回收这块内存(除非线程结束),但是从程序看这块内存应该是被回收掉了,会有内存泄漏(想处理只有线程被销毁才行,但是线程可能都是7*24小时运行的,所以可能会内存泄漏越来越多导致OOM),而使用弱引用,当tl变为null之后,指向ThreadLocal这块内存的只有key一个弱引用,一旦发生垃圾收集,就会被回收(弱引用的特性,只要垃圾回收期回收,弱引用就会被干掉),也就不存在内存泄漏。

value可能的内存泄漏问题及解决办法

这里需要注意的是,ThreadLocal被回收之后,key的值由原来的ThreadLocal变成了null,我们无法通过null值访问value指向的10M的空间,但是引用会一直存在,也是另外一种内存泄漏。所以我们每次使用完ThreadLocal之后一定要remove。(虽然执行get或者set的时候,会把entry里所有key为null的都清理掉,但是如果长时间没有get和set执行,这块泄漏就一直存在)

线程池使用ThreadLocal的处理

如果用的是线程池,用完线程之后要清理ThreadLocals,否则逻辑中如果有可以拿到threadlocal里的值就用原来的,可能会出现问题。这个比较有名的就是某大厂压测时候出现的压测标污染问题,造成大千万级重大损失,整个团队一窝端了。

自动清理主要看两个方法:

private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;// expunge entry at staleSlottab[staleSlot].value = null;tab[staleSlot] = null;size--;// Rehash until we encounter nullEntry e;int i;for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();if (k == null) {// 将k=null的entry置为nulle.value = null;tab[i] = null;size--;} else {// k不为null,则rehash从新分配配置int h = k.threadLocalHashCode & (len - 1);if (h != i) {tab[i] = null;// Unlike Knuth 6.4 Algorithm R, we must scan until// null because multiple entries could have been stale.while (tab[h] != null)// 重新分配后的位置上有元素则往后顺延。h = nextIndex(h, len);tab[h] = e;}}}return i;}

private boolean cleanSomeSlots(int i, int n) {boolean removed = false;Entry[] tab = table;int len = tab.length;do {i = nextIndex(i, len);Entry e = tab[i];if (e != null && e.get() == null) {n = len;removed = true;// 移除i = expungeStaleEntry(i);}} while ( (n >>>= 1) != 0);return removed;}

 ThreadLocal源码解析

先单独解释一下Entry的代码

static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);//new出来一个WeakReference对象,把ThreadLocal传给他,发生垃圾回收的时候key会被回收掉(只要指向它的强引用消失了发生GC必然回收,但是Entry是一个强引用,如果引用还在就不会整体回收)value = v;}}
/**虽然这么说不准确,但是为了记忆方便你可以认为除了Entry之外,ThreadLocalMap就是个HashMap,正常的面试不会考除这个类除了Entry之外的部分,保存数据的是Map里的Entry数组,Entry就是一个key value的键值对,value是我们常见的类型,而key是ThreadLocal*/
static class ThreadLocalMap {/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object).  Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table.  Such entries are referred to* as "stale entries" in the code that follows.*/static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);//new出来一个WeakReference对象,把ThreadLocal传给他,发生垃圾回收的时候key会被回收掉(只要指向它的强引用消失了发生GC必然回收,但是Entry是一个强引用,如果引用还在就不会整体回收)value = v;}}/*** The initial capacity -- MUST be a power of two.*/private static final int INITIAL_CAPACITY = 16;/*** The table, resized as necessary.* table.length MUST always be a power of two.*/private Entry[] table;/*** The number of entries in the table.*/private int size = 0;/*** The next size value at which to resize.*/private int threshold; // Default to 0/*** Set the resize threshold to maintain at worst a 2/3 load factor.*/private void setThreshold(int len) {threshold = len * 2 / 3;}/*** Increment i modulo len.*/private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}/*** Decrement i modulo len.*/private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1);}/*** Construct a new map initially containing (firstKey, firstValue).* ThreadLocalMaps are constructed lazily, so we only create* one when we have at least one entry to put in it.*/ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}/*** Construct a new map including all Inheritable ThreadLocals* from given parent map. Called only by createInheritedMap.** @param parentMap the map associated with parent thread.*/private ThreadLocalMap(ThreadLocalMap parentMap) {Entry[] parentTable = parentMap.table;int len = parentTable.length;setThreshold(len);table = new Entry[len];for (int j = 0; j < len; j++) {Entry e = parentTable[j];if (e != null) {@SuppressWarnings("unchecked")ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();if (key != null) {Object value = key.childValue(e.value);Entry c = new Entry(key, value);int h = key.threadLocalHashCode & (len - 1);while (table[h] != null)h = nextIndex(h, len);table[h] = c;size++;}}}}/*** Get the entry associated with key.  This method* itself handles only the fast path: a direct hit of existing* key. It otherwise relays to getEntryAfterMiss.  This is* designed to maximize performance for direct hits, in part* by making this method readily inlinable.** @param  key the thread local object* @return the entry associated with key, or null if no such*/private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);Entry e = table[i];if (e != null && e.get() == key)return e;elsereturn getEntryAfterMiss(key, i, e);}/*** Version of getEntry method for use when key is not found in* its direct hash slot.** @param  key the thread local object* @param  i the table index for key's hash code* @param  e the entry at table[i]* @return the entry associated with key, or null if no such*/private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {Entry[] tab = table;int len = tab.length;while (e != null) {ThreadLocal<?> k = e.get();if (k == key)return e;if (k == null)expungeStaleEntry(i);elsei = nextIndex(i, len);e = tab[i];}return null;}/*** Set the value associated with key.** @param key the thread local object* @param value the value to be set*/private void set(ThreadLocal<?> key, Object value) {// We don't use a fast path as with get() because it is at// least as common to use set() to create new entries as// it is to replace existing ones, in which case, a fast// path would fail more often than not.Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();if (k == key) {e.value = value;return;}if (k == null) {replaceStaleEntry(key, value, i);return;}}tab[i] = new Entry(key, value);int sz = ++size;if (!cleanSomeSlots(i, sz) && sz >= threshold)rehash();}/*** Remove the entry for key.*/private void remove(ThreadLocal<?> key) {Entry[] tab = table;int len = tab.length;int i = key.threadLocalHashCode & (len-1);for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) {if (e.get() == key) {e.clear();expungeStaleEntry(i);return;}}}/*** Replace a stale entry encountered during a set operation* with an entry for the specified key.  The value passed in* the value parameter is stored in the entry, whether or not* an entry already exists for the specified key.** As a side effect, this method expunges all stale entries in the* "run" containing the stale entry.  (A run is a sequence of entries* between two null slots.)** @param  key the key* @param  value the value to be associated with key* @param  staleSlot index of the first stale entry encountered while*         searching for key.*/private void replaceStaleEntry(ThreadLocal<?> key, Object value,int staleSlot) {Entry[] tab = table;int len = tab.length;Entry e;// Back up to check for prior stale entry in current run.// We clean out whole runs at a time to avoid continual// incremental rehashing due to garbage collector freeing// up refs in bunches (i.e., whenever the collector runs).int slotToExpunge = staleSlot;for (int i = prevIndex(staleSlot, len);(e = tab[i]) != null;i = prevIndex(i, len))if (e.get() == null)slotToExpunge = i;// Find either the key or trailing null slot of run, whichever// occurs firstfor (int i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();// If we find key, then we need to swap it// with the stale entry to maintain hash table order.// The newly stale slot, or any other stale slot// encountered above it, can then be sent to expungeStaleEntry// to remove or rehash all of the other entries in run.if (k == key) {e.value = value;tab[i] = tab[staleSlot];tab[staleSlot] = e;// Start expunge at preceding stale entry if it existsif (slotToExpunge == staleSlot)slotToExpunge = i;cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);return;}// If we didn't find stale entry on backward scan, the// first stale entry seen while scanning for key is the// first still present in the run.if (k == null && slotToExpunge == staleSlot)slotToExpunge = i;}// If key not found, put new entry in stale slottab[staleSlot].value = null;tab[staleSlot] = new Entry(key, value);// If there are any other stale entries in run, expunge themif (slotToExpunge != staleSlot)cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);}/*** Expunge a stale entry by rehashing any possibly colliding entries* lying between staleSlot and the next null slot.  This also expunges* any other stale entries encountered before the trailing null.  See* Knuth, Section 6.4** @param staleSlot index of slot known to have null key* @return the index of the next null slot after staleSlot* (all between staleSlot and this slot will have been checked* for expunging).*/private int expungeStaleEntry(int staleSlot) {Entry[] tab = table;int len = tab.length;// expunge entry at staleSlottab[staleSlot].value = null;tab[staleSlot] = null;size--;// Rehash until we encounter nullEntry e;int i;for (i = nextIndex(staleSlot, len);(e = tab[i]) != null;i = nextIndex(i, len)) {ThreadLocal<?> k = e.get();if (k == null) {e.value = null;tab[i] = null;size--;} else {int h = k.threadLocalHashCode & (len - 1);if (h != i) {tab[i] = null;// Unlike Knuth 6.4 Algorithm R, we must scan until// null because multiple entries could have been stale.while (tab[h] != null)h = nextIndex(h, len);tab[h] = e;}}}return i;}/*** Heuristically scan some cells looking for stale entries.* This is invoked when either a new element is added, or* another stale one has been expunged. It performs a* logarithmic number of scans, as a balance between no* scanning (fast but retains garbage) and a number of scans* proportional to number of elements, that would find all* garbage but would cause some insertions to take O(n) time.** @param i a position known NOT to hold a stale entry. The* scan starts at the element after i.** @param n scan control: {@code log2(n)} cells are scanned,* unless a stale entry is found, in which case* {@code log2(table.length)-1} additional cells are scanned.* When called from insertions, this parameter is the number* of elements, but when from replaceStaleEntry, it is the* table length. (Note: all this could be changed to be either* more or less aggressive by weighting n instead of just* using straight log n. But this version is simple, fast, and* seems to work well.)** @return true if any stale entries have been removed.*/private boolean cleanSomeSlots(int i, int n) {boolean removed = false;Entry[] tab = table;int len = tab.length;do {i = nextIndex(i, len);Entry e = tab[i];if (e != null && e.get() == null) {n = len;removed = true;i = expungeStaleEntry(i);}} while ( (n >>>= 1) != 0);return removed;}/*** Re-pack and/or re-size the table. First scan the entire* table removing stale entries. If this doesn't sufficiently* shrink the size of the table, double the table size.*/private void rehash() {expungeStaleEntries();// Use lower threshold for doubling to avoid hysteresisif (size >= threshold - threshold / 4)resize();}/*** Double the capacity of the table.*/private void resize() {Entry[] oldTab = table;int oldLen = oldTab.length;int newLen = oldLen * 2;Entry[] newTab = new Entry[newLen];int count = 0;for (int j = 0; j < oldLen; ++j) {Entry e = oldTab[j];if (e != null) {ThreadLocal<?> k = e.get();if (k == null) {e.value = null; // Help the GC} else {int h = k.threadLocalHashCode & (newLen - 1);while (newTab[h] != null)h = nextIndex(h, newLen);newTab[h] = e;count++;}}}setThreshold(newLen);size = count;table = newTab;}/*** Expunge all stale entries in the table.*/private void expungeStaleEntries() {Entry[] tab = table;int len = tab.length;for (int j = 0; j < len; j++) {Entry e = tab[j];if (e != null && e.get() == null)expungeStaleEntry(j);}}}

ThreadLocal的其他的常规代码

/** Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.** This code is free software; you can redistribute it and/or modify it* under the terms of the GNU General Public License version 2 only, as* published by the Free Software Foundation.  Oracle designates this* particular file as subject to the "Classpath" exception as provided* by Oracle in the LICENSE file that accompanied this code.** This code is distributed in the hope that it will be useful, but WITHOUT* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or* FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License* version 2 for more details (a copy is included in the LICENSE file that* accompanied this code).** You should have received a copy of the GNU General Public License version* 2 along with this work; if not, write to the Free Software Foundation,* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.** Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA* or visit www.oracle.com if you need additional information or have any* questions.*/package java.lang;
import jdk.internal.misc.TerminatingThreadLocal;import java.lang.ref.*;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;/*** This class provides thread-local variables.  These variables differ from* their normal counterparts in that each thread that accesses one (via its* {@code get} or {@code set} method) has its own, independently initialized* copy of the variable.  {@code ThreadLocal} instances are typically private* static fields in classes that wish to associate state with a thread (e.g.,* a user ID or Transaction ID).** <p>For example, the class below generates unique identifiers local to each* thread.* A thread's id is assigned the first time it invokes {@code ThreadId.get()}* and remains unchanged on subsequent calls.* <pre>* import java.util.concurrent.atomic.AtomicInteger;** public class ThreadId {*     // Atomic integer containing the next thread ID to be assigned*     private static final AtomicInteger nextId = new AtomicInteger(0);**     // Thread local variable containing each thread's ID*     private static final ThreadLocal&lt;Integer&gt; threadId =*         new ThreadLocal&lt;Integer&gt;() {*             &#64;Override protected Integer initialValue() {*                 return nextId.getAndIncrement();*         }*     };**     // Returns the current thread's unique ID, assigning it if necessary*     public static int get() {*         return threadId.get();*     }* }* </pre>* <p>Each thread holds an implicit reference to its copy of a thread-local* variable as long as the thread is alive and the {@code ThreadLocal}* instance is accessible; after a thread goes away, all of its copies of* thread-local instances are subject to garbage collection (unless other* references to these copies exist).** @author  Josh Bloch and Doug Lea* @since   1.2*/
public class ThreadLocal<T> {/*** ThreadLocals rely on per-thread linear-probe hash maps attached* to each thread (Thread.threadLocals and* inheritableThreadLocals).  The ThreadLocal objects act as keys,* searched via threadLocalHashCode.  This is a custom hash code* (useful only within ThreadLocalMaps) that eliminates collisions* in the common case where consecutively constructed ThreadLocals* are used by the same threads, while remaining well-behaved in* less common cases.*/private final int threadLocalHashCode = nextHashCode();/*** The next hash code to be given out. Updated atomically. Starts at* zero.*/private static AtomicInteger nextHashCode =new AtomicInteger();/*** The difference between successively generated hash codes - turns* implicit sequential thread-local IDs into near-optimally spread* multiplicative hash values for power-of-two-sized tables.*/private static final int HASH_INCREMENT = 0x61c88647;/*** Returns the next hash code.*/private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);}/*** Returns the current thread's "initial value" for this* thread-local variable.  This method will be invoked the first* time a thread accesses the variable with the {@link #get}* method, unless the thread previously invoked the {@link #set}* method, in which case the {@code initialValue} method will not* be invoked for the thread.  Normally, this method is invoked at* most once per thread, but it may be invoked again in case of* subsequent invocations of {@link #remove} followed by {@link #get}.** <p>This implementation simply returns {@code null}; if the* programmer desires thread-local variables to have an initial* value other than {@code null}, {@code ThreadLocal} must be* subclassed, and this method overridden.  Typically, an* anonymous inner class will be used.** @return the initial value for this thread-local* 这个就是返回一个null,不知道这么写目的何在*/protected T initialValue() {return null;}/*** Creates a thread local variable. The initial value of the variable is* determined by invoking the {@code get} method on the {@code Supplier}.** @param <S> the type of the thread local's value* @param supplier the supplier to be used to determine the initial value* @return a new thread local variable* @throws NullPointerException if the specified supplier is null* @since 1.8*/public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {return new SuppliedThreadLocal<>(supplier);}/*** Creates a thread local variable.* @see #withInitial(java.util.function.Supplier)*/public ThreadLocal() {}/*** Returns the value in the current thread's copy of this* thread-local variable.  If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local根据当前线程拿到ThreadLocalMap(就是返回当前线程的ThreadLocals(Thread类型的t里的ThreadLocalMap)),如果不为空,直接设置到那个ThreadLocalMap里(key是当前的ThreadLocal对象,value是当前我们传入的value)*/public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {/**根据当前ThreadLocal的引用从map里拿到对应的Entry*/ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")/**如果拿到Entry就返回entry的value*/T result = (T)e.value;return result;}}return setInitialValue();}/*** Returns {@code true} if there is a value in the current thread's copy of* this thread-local variable, even if that values is {@code null}.** @return {@code true} if current thread has associated value in this*         thread-local variable; {@code false} if not*/boolean isPresent() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);return map != null && map.getEntry(this) != null;}/*** Variant of set() to establish initialValue. Used instead* of set() in case user has overridden the set() method.** @return the initial value*/private T setInitialValue() {/**拿到初始值,其实就是null*/T value = initialValue();/**拿到当前线程*/Thread t = Thread.currentThread();/**拿到线程的ThreadLocalMap*/ThreadLocalMap map = getMap(t);/**如果map不为null设置为上面拿到的初始值*/if (map != null) {map.set(this, value);} else {/**如果map没有初始化,初始化map*/createMap(t, value);}if (this instanceof TerminatingThreadLocal) {TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);}return value;}/*** Sets the current thread's copy of this thread-local variable* to the specified value.  Most subclasses will have no need to* override this method, relying solely on the {@link #initialValue}* method to set the values of thread-locals.** @param value the value to be stored in the current thread's copy of*        this thread-local.*/public void set(T value) {/**先拿到当前的线程*/Thread t = Thread.currentThread();/**拿到当前线程对应的ThreadLocalMap*/ThreadLocalMap map = getMap(t);/**如果之前已经初始化过map了,直接设置值*/if (map != null) {map.set(this, value);} else {/**如果map没有初始化,初始化map并把当前ThreadLocal作为key,value作为值放入map*/createMap(t, value);}}/*** Removes the current thread's value for this thread-local* variable.  If this thread-local variable is subsequently* {@linkplain #get read} by the current thread, its value will be* reinitialized by invoking its {@link #initialValue} method,* unless its value is {@linkplain #set set} by the current thread* in the interim.  This may result in multiple invocations of the* {@code initialValue} method in the current thread.** @since 1.5*/public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null) {m.remove(this);}}/*** Get the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param  t the current thread* @return the map*/ThreadLocalMap getMap(Thread t) {return t.threadLocals;}/*** Create the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param t the current thread* @param firstValue value for the initial entry of the map*/void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}/*** Factory method to create map of inherited thread locals.* Designed to be called only from Thread constructor.** @param  parentMap the map associated with parent thread* @return a map containing the parent's inheritable bindings*/static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);}/*** Method childValue is visibly defined in subclass* InheritableThreadLocal, but is internally defined here for the* sake of providing createInheritedMap factory method without* needing to subclass the map class in InheritableThreadLocal.* This technique is preferable to the alternative of embedding* instanceof tests in methods.*/T childValue(T parentValue) {throw new UnsupportedOperationException();}/*** An extension of ThreadLocal that obtains its initial value from* the specified {@code Supplier}.*/static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {private final Supplier<? extends T> supplier;SuppliedThreadLocal(Supplier<? extends T> supplier) {this.supplier = Objects.requireNonNull(supplier);}@Overrideprotected T initialValue() {return supplier.get();}}
}

ThreadLocal的应用场景

hreadLocal在工作中主要的应用场景包括但不限于以下几个方面:

  1. 线程间数据隔离

    • 在多线程环境中,每个线程可能需要维护自己的独立状态,如数据库连接、用户会话信息或事务上下文。ThreadLocal为每个线程提供独立的变量副本,避免了线程间的共享和同步问题,从而实现了线程间的数据隔离。
    • 例如,在Spring的事务管理中,事务管理器通过AOP切入业务代码,在进入业务代码前,会依据相应的事务管理器提取出相应的事务对象,并将其保存在ThreadLocal中。这样,线程内多次获取到的Connection对象是同一个,从而保证了事务的一致性。
  2. 简化参数传递

    • 在同一个线程的执行过程中,可以通过ThreadLocal存储和访问数据,避免了将数据作为参数在多个方法之间传递的繁琐。
    • 例如,在日志记录中,可以使用ThreadLocal存储与当前线程相关的日志上下文,如用户ID或事务ID。这样,在日志消息中包含这些特定于线程的信息时,可以直接从ThreadLocal中获取,而无需通过参数传递。
  3. 存储线程安全对象

    • 对于一些不是线程安全的类,如SimpleDateFormat,可以使用ThreadLocal为每个线程创建一个独立的实例,从而避免线程安全问题。
    • 通过这种方式,每个线程都可以独立地、安全地操作自己的SimpleDateFormat实例,而不会影响到其他线程。
  4. 跨层传递参数

    • 在一些复杂的业务逻辑中,可能需要跨层传递参数。使用ThreadLocal可以避免在方法之间传递参数的繁琐,简化代码结构。
    • 特别是在一些框架或中间件中,如Spring MVC或MyBatis,ThreadLocal经常被用于存储和传递与当前线程相关的上下文信息。
  5. 用户身份信息存储

    • 在很多应用中,都需要做登录鉴权。一旦鉴权通过之后,就可以把用户信息存储在ThreadLocal中。这样在后续的所有流程中,需要获取用户信息的,直接取ThreadLocal中获取即可,非常方便。

然而,使用ThreadLocal时也需要注意一些潜在的问题。例如,如果线程长时间存在而ThreadLocal变量没有及时清理,就可能导致内存泄漏。因此,在不需要使用ThreadLocal时,应及时调用其remove()方法来清理变量。

总的来说,ThreadLocal是一个强大的工具,它提供了一种简单而高效的方式来为每个线程维护独立的变量副本,避免了同步问题,提高了多线程程序的并发性和安全性。但同时也需要谨慎使用,以避免潜在的问题。

相关文章:

ThreadLocal原理解析及面试

基本使用 讲原理之前&#xff0c;我简单写个demo小程序说说怎么使用 public class TestThreadLocal {public static void main(String[] args) throws InterruptedException {ThreadLocal<String> tl new ThreadLocal();/**主线程设置了一个值*/tl.set("SSSSSs&…...

探索未来:mosquitto-python,AI领域的新宠

文章目录 探索未来&#xff1a;mosquitto-python&#xff0c;AI领域的新宠背景&#xff1a;为何选择mosquitto-python&#xff1f;库简介&#xff1a;mosquitto-python是什么&#xff1f;安装指南&#xff1a;如何安装mosquitto-python&#xff1f;函数用法&#xff1a;5个简单…...

C++版iwanna1

第一篇目录 开头程序Game.cpp源文件Player.h头文件Player.cpp源文件trigger.h头文件trigger.cpp源文件Cmp.h头文件Cmp.cpp源文件 开头 大家好&#xff0c;我叫这是我58。 程序 Game.cpp源文件 #define _CRT_SECURE_NO_WARNINGS 1 #include <iostream> #include <c…...

LSTM变种模型

一、GRU 1.概念 GRU&#xff08;门控循环单元&#xff0c;Gated Recurrent Unit&#xff09;是一种循环神经网络&#xff08;RNN&#xff09;的变体&#xff0c;旨在解决标准 RNN 在处理长期依赖关系时遇到的梯度消失问题。GRU 通过引入门控机制简化了 LSTM&#xff08;长短期…...

Python进阶--函数进阶

目录 1. 函数多返回值 2. 函数多种传参方式 (1). 位置参数 (2). 关键字参数 (3). 缺省参数 (4). 不定长参数 3. 匿名函数 (1). 函数作为参数传递 (2). lambda匿名函数 1. 函数多返回值 def return_num():return 1# 返回1之后就不会再向下继续执行函数体return 2 resu…...

elasticsearch 8.2 设置账号密码

背景:单节点集群数据写入测试-CSDN博客 前述项目支持设置账号密码,但8+版本似乎不能那么做了。 ERROR: [1] bootstrap checks failed. You must address the points described in the following [1] lines before starting Elasticsearch. bootstrap check failure [1] of…...

JavaScript代码如何测试?

测试JavaScript代码是确保其功能、性能和可靠性的关键步骤。以下是一些详细的步骤和方法&#xff0c;用于测试JavaScript代码&#xff1a; 1、编写测试用例 首先&#xff0c;你需要为要测试的JavaScript代码编写测试用例。这些用例应该涵盖代码的各种功能和场景&#xff0c;包…...

案例分享—国外ui设计优秀案例

国外企业更注重用户体验设计&#xff0c;倾向于简洁清晰的设计风格&#xff0c;以提高用户的使用体验和操作效率。他们注重“简约至上”的设计理念&#xff0c;认为简洁的设计可以减少用户的认知负担&#xff0c;提高用户的工作效率。 设计师在界面设计中往往更加注重创意性和个…...

在JavaScript中,改变this指向的call,apply,bind有什么区别,原理分别是什么?

在JavaScript中&#xff0c;call、apply和bind方法都是用来改变函数执行时this指向的。 以下通过一个Demo帮助理解&#xff0c;代码如下&#xff1a; var obj {name: lisi,sayHello: function() {console.log(this.name)} } obj.sayHello()// lisifunction sayHello() {conso…...

Redis 缓存策略详解:提升性能的四种常见模式

在现代分布式系统中&#xff0c;缓存是提升性能和减轻数据库负载的关键组件。Redis 作为一种高性能的内存数据库&#xff0c;被广泛应用于缓存层。本文将深入探讨几种常用的 Redis 缓存策略&#xff0c;包括旁路缓存模式&#xff08;Cache-Aside Pattern&#xff09;、读穿透模…...

怎么建设网站吸引并留住客户

如何建设网站吸引并留住客户 在当今数字化时代&#xff0c;网站是企业与客户沟通的重要桥梁。一个设计精良、功能完备的网站不仅能吸引客户的注意&#xff0c;还能有效留住他们。以下是一些建设网站的关键策略。 **1. 用户体验优先** 网站的整体用户体验&#xff08;UX&#x…...

培训行业为什么要搭建自己的知识付费小程序平台?集师知识付费系统 集师知识付费小程序 集师知识服务系统 集师线上培训系统 集师线上卖课小程序

在当今这个信息爆炸的时代&#xff0c;培训行业正面临前所未有的变革与挑战。传统的线下授课模式虽然经典&#xff0c;但在互联网技术的冲击下&#xff0c;其局限性日益凸显。为了更好地适应市场需求&#xff0c;提升服务效率与用户体验&#xff0c;培训行业亟需搭建自己的知识…...

Linux:Linux进程概念

✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走下来! 文章目录 目录 文章目录 前言 一 冯诺依曼体系结构 二 操作系统(Operator System&#xff09; 2.1 概念 2.2 设计OS的目的 ​编辑 2.3 OS如何进行管理 ​编辑2.4 总结 三 进程的标示符 3.1 基本概念…...

专题九_递归_算法专题详细总结

目录 递归 1.什么是递归&#xff1f; 2.为什么会用到递归&#xff1f; 3.如何理解递归&#xff1f; 1.递归展开的细节图 2.二叉树中的题目 3.宏观看待递归的过程 1) 不要在意细节的展开图 2) 把递归的函数当作一个黑盒 3) 相信这个黑盒一定能够完成这个任务 4.如何写…...

性能赶超GPT-4!多模态检索最新成果刷爆SOTA!顶会思路确定不学?

关注各大顶会的同学们都知道&#xff0c;今年多模态相关的主题可谓是火爆非常&#xff0c;有许多突破性成果被提出&#xff0c;比如最新的多模态检索增强框架MORE&#xff0c;生成性能猛超GPT-4&#xff01; 再比如多模态检索模型MARVEL&#xff0c;在所有基准上实现SOTA&…...

基于 Qwen2.5-0.5B 微调训练 Ner 命名实体识别任务

一、Qwen2.5 & 数据集 Qwen2.5 是 Qwen 大型语言模型的最新系列&#xff0c;参数范围从 0.5B 到 72B 不等。 对比 Qwen2 最新的 Qwen2.5 进行了以下改进&#xff1a; 知识明显增加&#xff0c;并且大大提高了编码和数学能力。在指令跟随、生成长文本&#xff08;超过 8K…...

16【Protues51单片机仿真】智能洗衣机倒计时系统

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 用直流电机转动模拟洗衣机。要求 有弱洗、普通洗、强洗三种模式&#xff0c;可通过按键选择。可以设置洗衣时长&#xff0c;通关按键选择15、30、45、60、90分钟。时间到蜂鸣器报警提示。LCD 显示…...

爱心曲线公式大全

local r a*((math.sin(angle) * math.sqrt(math.abs(math.cos(angle)))) / (math.sin(angle) 1.4142) - 2 * math.sin(angle) 2) local x r * math.cos(angle) -- 计算对应的x值 local z r * math.sin(angle) 1.5*a - --曲线公式绘画 local function generateParabola()…...

新书速览|你好,C++

《你好&#xff0c;C》 本书内容 《你好&#xff0c;C》主要介绍C开发环境的搭建、基础语法知识、面向对象编程思想以及标准模板库的应用&#xff0c;特别针对初学者在学习C过程中可能遇到的难点提供了解决方案。全书共分13章&#xff0c;以一个工资程序的不断优化和完善为线索…...

ufw:Linux网络防火墙

一、命令简介 ​ufw​&#xff08;Uncomplicated Firewall&#xff09;是一个为 Linux 系统提供简单易用的命令行界面的防火墙管理工具。它是基于 iptables ​的&#xff0c;但提供了更简洁的语法和更直观的操作方式&#xff0c;使得配置防火墙变得更加简单&#xff0c;特别适…...

[C++]使用纯opencv部署yolov11-cls图像分类onnx模型

【算法介绍】 在C中使用纯OpenCV部署YOLOv11-cls图像分类ONNX模型是一项具有挑战性的任务&#xff0c;因为YOLOv11通常是用PyTorch等深度学习框架实现的&#xff0c;而OpenCV本身并不直接支持加载和运行PyTorch模型。然而&#xff0c;可以通过一些间接的方法来实现这一目标&am…...

​​​​​​​如何使用Immersity AI将图片转换成3D效果视频

随着技术的进步&#xff0c;图片处理变得越来越强大和直观。借助Immersity AI这样的工具&#xff0c;我们现在可以轻松地将平面图片转换成3D效果视频。以下是如何使用Immersity AI进行这一转换的详细步骤。 第一步&#xff1a;访问Immersity AI网站 首先&#xff0c;打开你的…...

安全运营 -- GPO审计

0x00 背景 审计GPO&#xff0c;目的是审计哪些GPO权限分配不合理&#xff0c;包括但不限于审计预期以外的用户具有对GPO的写权限。 0x01 开启审核 在一台windows服务器上 开始 -- 运行 -- 输入 server manager 依次点击Manage -- Add Roles and Features Wizard 角色和功能…...

thinkphp6入门(25)-- 分组查询 GROUP_CONCAT

假设表名为 user_courses&#xff0c;字段为 user_id 和 course_name&#xff0c;存储每个用户选修的课程&#xff0c;想查询每个学生选修的所有课程 SQL 原生查询 SELECT user_id, GROUP_CONCAT(course_name) as courses FROM user_courses GROUP BY user_id; ThinkPHP 代码…...

小米 MIX FOLD工程固件 更换字库修复分区 资源预览与刷写说明

小米 MIX FOLD机型代号 :cetus 该手机搭载骁龙888旗舰处理器 。对于一些因为字库问题损坏导致的故障,更换字库后要先刷写对应的工程底层修复固件。绑定cpu后在写入miui量产固件。 通过博文了解 1💝💝💝-----此机型工程固件的资源刷写注意事项 2💝💝💝-----此…...

Flutter全局统一自定义导航栏返回按钮

Flutter全局统一自定义导航栏返回按钮 在Flutter开发中&#xff0c;导航栏&#xff08;AppBar&#xff09;是用户界面的重要组成部分&#xff0c;它不仅提供了页面标题&#xff0c;还可能包含返回按钮、导航按钮等。默认情况下&#xff0c;每个Scaffold的AppBar都会包含一个返…...

微信图片的超能力:5大隐秘功能揭秘,让你成为信息处理大师

在数字化时代&#xff0c;微信已成为我们日常生活中不可或缺的通讯工具。 它不仅仅是聊天的平台&#xff0c;更是一个功能强大的信息处理工具。 今天&#xff0c;我们将揭秘微信中图片背后的五大隐秘功能&#xff0c;让你在使用微信时更加得心应手&#xff0c;成为信息处理的…...

python实现RC4加解密算法

RC4算法 一、算法介绍1.1 背景1.2 密钥调度算法(KSA)1.3 伪随机生成算法(PRGA) 二、代码实现三、演示效果 一、算法介绍 1.1 背景 RC4算法是由Ron Rivest在1987年为RSA数据安全公司设计的一种流密码算法&#xff0c;其安全性主要依赖于其密钥流的随机性和不可预测性。该算法因…...

BLE MESH学习2——自定义MESH网络架构思考

BLE MESH学习2——自定义MESH网络架构思考 基于对WCH CH582这款单片机的了解&#xff0c;其可以实现mesh配网、朋友节点、低功耗节点和中继节点的角色&#xff0c;基本功能无问题。在此基础上&#xff0c;考虑满足IoT需求的MESH架构设计&#xff0c;作为后续设计的“白皮书”。…...

路由器的工作机制

在一个家庭或者一个公司中 路由器的作用主要有两个(①路由–决定了数据包从来源到目的地的路径 通过映射表决定 ②转送–通过路由器知道了映射表 就可以将数据包从路由器的输入端转移给合适的输出端) 我们可以画一张图来分析一下&#xff1a; 我们好好来解析一下这张图&#x…...