Thread isolation mechanism in Java.
ThreadLocal provides a way to isolate variable access per thread, ensuring thread-safe manipulation of shared data in a multi-threaded environment.
static ThreadLocal<Integer> counter = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0;
}
};
public static void main(String[] args) {
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++) {
threads[i] = new Thread(() -> {
int val = counter.get();
counter.set(val + 5);
System.out.println(Thread.currentThread().getName() + "-" + val);
});
}
for (int i = 0; i < 5; i++) {
threads[i].start();
}
}
How ThreadLocal Works Internally
The set() Method
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
On the first invocation when the map doesn't exist, a ThreadLocalMap is created lazily.
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
When the map already exists, the following steps occur:
- Compute the array index for the Entry using hash-based slot calculation
- Use linear probing to traverse from index
ito the end of the array - If the map contains the same key, the existing value gets overwritten
- If the key in the map is null, replace it with the new key-value pair and clean up stale entries
- Trigger rehash扩容 when threshold is exceeded
private void set(ThreadLocal<?> key, Object value) {
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();
}
Linear Probing is an open addressing strategy used to resolve hash collitions. A hash table maps keys to array positions via a hash function for fast data access. When two keys hash to the same position, linear probing handles the conflict:
- Insertion: Find the nearest empty slot after the collision point and place the new key-value pair there
- Lookup: Starting from the hashed position, search forward until finding the matching key or an empty slot
replaceStaleEntry
This method handles stale entry cleanup and replacement.
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
int staleSlot) {
Entry[] tab = table;
int len = tab.length;
Entry e;
int slotToExpunge = staleSlot;
for (int i = prevIndex(staleSlot, len);
(e = tab[i]) != null;
i = prevIndex(i, len))
if (e.get() == null)
slotToExpunge = i;
for (int i = nextIndex(staleSlot, len);
(e = tab[i]) != null;
i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
tab[i] = tab[staleSlot];
tab[staleSlot] = e;
if (slotToExpunge == staleSlot)
slotToExpunge = i;
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
return;
}
if (k == null && slotToExpunge == staleSlot)
slotToExpunge = i;
}
tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);
if (slotToExpunge != staleSlot)
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}
0x61c88647: The Fibonacci Hash Constant
private static final int HASH_INCREMENT = 0x61c88647;
public static void main(String[] args) {
magicHash(16);
magicHash(32);
}
private static void magicHash(int size) {
int hashCode = 0;
for (int i = 0; i < size; i++) {
hashCode = i * HASH_INCREMENT + HASH_INCREMENT;
System.out.print((hashCode & (size - 1)) + " ");
}
System.out.println();
}
Output:
7 14 5 12 3 10 1 8 15 6 13 4 11 2 9 0
7 14 21 28 3 10 17 24 31 6 13 20 27 2 9 16 23 30 5 12 19 26
1 8 15 22 29 4 11 18 25 0
This magic number produces a distributed sequence that minimizes clustering in the hash table.
Why ThreadLocal Uses Weak References
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
- A weak reference allows garbage collection even when only weakly referenced. When the next GC runs and an object has only weak references pointing to it, that object gets collected
- ThreadLocalMap's Entry keys are weak references. When a key becomes null, both
get()andset()operations will clean up these stale entries - If keys were strong references, setting
threadLocal = nullwould still leavethread -> threadLocalMap -> entry -> key (threadLocal). This would prevent the Entry value from being garbage clolected until the ThreadLocal instance is no longer in use, causing a memory leak - With thread pools, if values aren't menually cleared, they persist in entries indefinitely, leading to memory leaks
Bitwise AND Operator Reference
The & operator performs bitwise AND on binary representations. Both bits must be 1 for the result to be 1.
Example: 132 & 15 = ?
- 132 in binary: 10000100
- 15 in binary: 00001111
- Result: 10000100 & 00001111 = 00000100 = 4
Rules:
- When bits differ (1 and 0), the result is 0
- When bits match (both 1), the smaller value wins in the final result