一、ThreadLocalMap的定义:
static class ThreadLocalMap
ThreadLocalMap类是ThreadLocal类的一个内部类,关于ThreadLocal类可以查看上一篇。
二、ThreadLocalMap的成员变量:
ThreadLocalMap类有4个成员变量:1个常量、3个变量。
/** * 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
分别为:存放元素的数组、数组初始大小、数组元素的个数、阀值。
三、ThreadLocalMap的构造方法:
1. 第一个构造方法:带2个参数(默认的构造方法)。
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);
}
先对table进行初始化,默认大小为INITIAL_CAPACITY。然后计算第一对键值对要存放的位置,即在table中的下标,然后为这个键值对在数组相应的下标下创建一个Entry对象,最后设置阈值。在这里它的阈值为table长度的2/3。
2. 第二个构造方法:带1个参数(私有构造方法)。
/**
* 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) {
ThreadLocal key = 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++;
}
}
}
}
改私有的构造方法是由ThreadLocal里的createInheritedMap()中调用的,这个方法会由Thread里的构造方法来调用(在Thread类里的init()方法来调用,而init()方法只被Thread里的构造方法调用)。
四、ThreadLocalMap的set方法:
/** * 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(); } /** * 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 first for (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 exists if (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 slot tab[staleSlot].value = null; tab[staleSlot] = new Entry(key, value); // If there are any other stale entries in run, expunge them if (slotToExpunge != staleSlot) cleanSomeSlots(expungeStaleEntry(slotToExpunge), len); }
在set()方法中,首先通过key(threadLocal)中的hashc值与table的总长度取模,然后根据取模后的值作为下标,找到table当中下标为此值的entry,判断该entry是否存在,如果存在,判断entry里的key与value,如果与当前要保存的key与value相同的话就不保存直接返回。如果entry里的key为null的话,就替换为当前要保存的key与value。否则就是碰撞的情况了,这时就调用nextIndex()方法计算下一个坐标。
如果计算后的坐标获取到的entry为null,就new一个Entry对象并保存进去,然后调用cleanSomeSlots()对table进行清理,如果没有任何Entry被清理,并且表的size超过了阈值,就会调用rehash()方法。
cleanSomeSlots()会调用expungeStaleEntry清理陈旧过时的Entry。rehash则会调用expungeStaleEntries()方法清理所有的陈旧的Entry,然后在size大于阈值的3/4时调用resize()方法进行扩容。
五、ThreadLocalMap的get方法:
/** * 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; else return getEntryAfterMiss(key, i, e); } /** * Version of getEntry method for use when key is not found in * its direct hash slot. * * @param key the thread local object * @param i the table index for key's hash code * @param e the entry at table[i] * @return the entry associated with key, or null if no such */ private Entry getEntryAfterMiss(ThreadLocal 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); else i = nextIndex(i, len); e = tab[i]; } return null; }
getEntry()方法通过计算出的下标从table中取出entry,如果取得的entry为null或它的key值不相等,就调用getEntryAfterMiss()方法,否则返回。
而在getEntryAfterMiss()是当通过key与table的长度取模得到的下标取得entry后,entry里没有该key时所调用的。这时,如果获取的entry为null,即没有保存,就直接返回null,否则进入循环不,计算下一个坐标并获取对应的entry,并且当key相等时(表明找到了之前保存的值)返回entry,或是entry为null时退出循环,并返回null。
六、ThreadLocalMap的remove方法:
/** * 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; } } }
计算到下标后,如果取得的entry的key与ThreadLocal相同,就调用Entry的clear方法把弱引用设置为null,然后调用expungeStaleEntry对table进行清理。
相关推荐
在JDK 8之前,ThreadLocalMap的实现是一个静态内部类,它不是HashMap,而是基于Entry数组实现的。Entry继承自WeakReference,这意味着ThreadLocal对象如果不再被引用,可以被垃圾收集器回收,避免内存泄漏。然而,...
### 知识点七:测试用例 测试用例可以帮助我们更好地理解ThreadLocal的使用和潜在问题: - 创建多个线程,每个线程使用ThreadLocal变量,如果变量引用的是同一个共享对象,修改其中一个线程的变量值,其他线程是否...
同时,`ThreadLocalMap`的构造、扩容、收缩以及弱引用的处理等都是源码分析的重点。 总的来说,ThreadLocal是一个强大的工具,可以有效解决多线程环境中的数据隔离问题,但在使用过程中需要注意内存管理,防止潜在...
以上是对ThreadLocal的简单介绍和源码分析,实际使用中还需要结合具体业务场景进行合理设计和优化。通过深入理解ThreadLocal的工作原理,可以帮助我们更好地利用这一工具,提高代码的并发性能和可维护性。
ThreadLocal的内部维护了一个ThreadLocalMap,它是基于弱引用的HashMap。每个线程都有自己的ThreadLocalMap,键是ThreadLocal实例,值是线程局部变量的副本。弱引用的设计使得当ThreadLocal实例不再被外部引用时,...
ThreadLocalMap是ThreadLocal的内部类,它实现了键值对的数据结构,为每个线程维护了一个线程局部变量的副本。 ThreadLocalMap的getEntry方法用于获取当前线程ThreadLocal变量对应的值。在ThreadLocalMap中,每个...
Java并发编程学习之ThreadLocal源码详析 ThreadLocal是Java并发编程中的一种机制,用于解决多线程访问共享变量的问题。它可以使每个线程对共享变量的访问都是线程安全的,使得多线程编程变得更加简单。 ...
七、ThreadLocalMap 中的 hash 冲突是如何处理的 ThreadLocalMap 中的 hash 冲突是通过 ThreadLocal 对象的 hash 值来解决的。当我们创建一个 ThreadLocal 对象时,它的 hash 值将被计算出来,并被用来作为 ...
ThreadLocal 的原理、源码深度分析及使用 ThreadLocal 是 Java 语言中的一种机制,用于实现线程本地存储,能够帮助开发者在多线程环境下解决变量访问安全的问题。下面将对 ThreadLocal 的原理、实现机制、使用方法...
基于链表实现队列、基于循环数组实现队列 : 栈 : 堆 : 集合与映射 :Hash 表 树形结构: : 二分查找树 : 平衡树 : 红黑树 : 线段树 : 字典树 其他: : 图 : 跳表 : 并查集 JDK 集合源码 基本 List, Set, ...
然后,通过当前线程获取对应的`ThreadLocalMap`实例,并基于`ThreadLocal`对象作为键查找对应的值。 2. **设置线程局部变量**:设置线程局部变量的过程类似。首先,获取当前线程和对应的`ThreadLocalMap`。如果当前...
在分析ThreadLocal源码时,可以了解到它如何在内部实现线程隔离,以及ThreadLocalMap的结构和工作方式。理解这些细节有助于我们更好地利用ThreadLocal,同时避免可能出现的问题。通过阅读源码,我们可以发现...
掌握这些知识点并结合源码分析、性能优化和实际应用,能够显著提高面试者在一线大厂Java多线程面试中的竞争力,有助于提升职业发展。建议学习者按照课程大纲的顺序,结合实践深入理解,避免仅仅死记硬背题目。
ThreadLocalMap的实现使用了弱引用(WeakReference<ThreadLocal<?>>)作为key,这是因为即使没有外部引用指向ThreadLocal实例,只要它还在ThreadLocalMap中作为key存在,垃圾收集器就不会回收ThreadLocal实例。...
**源码分析** ThreadLocal的核心方法是`get()`、`set()`和`remove()`。`get()`方法首先查找当前线程的ThreadLocalMap,然后返回与当前ThreadLocal实例关联的值。`set()`方法会在ThreadLocalMap中创建一个新的Entry...
`ThreadLocalMap`中的每个条目是一个`Entry`对象,它不是`HashMap`中的`Entry`,而是专为`ThreadLocalMap`设计的。`Entry`的key是`ThreadLocal`实例,value是用户存储的对象。`Entry`使用弱引用作为key,这样在`...
ThreadLocalMap的实现基于openjdk11的源码,下面是相关的源码分析: 1. get方法:ThreadLocal的get方法会首先检查当前线程是否已经设置了该线程局部变量,如果已经设置了则直接返回,如果没有设置则调用setInitial...
- **实现原理**:通过ThreadLocalMap存储线程局部变量,使用Thread对象的ThreadLocalMap属性来实现线程隔离。 #### JVM中的共享区与GC Root - **共享区**:如方法区、字符串常量池等。 - **GC Root**:包括静态变量...
当ThreadLocal的get()或set()方法被调用时,会检查当前线程的ThreadLocalMap,如果不存在,就会创建一个新的ThreadLocalMap。 下面是一个简单的ThreadLocal使用示例: ```java public class ThreadLocalSample { ...