ThreadLocal实现轻量数据共享的原理——源码

本文详细解读ThreadLocal的工作原理,探讨其如何实现线程局部变量的独立副本,以及ThreadLocalMap的内部结构与数据操作。重点介绍哈希碰撞方法、原子类的应用和线程生命周期的关联处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

ThreadLocal实现轻量数据共享的原理——源码

此类提供线程局部变量。 这些变量与普通变量不同,因为每个访问一个线程(通过其get或set方法)的线程都有其自己的,独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或事务ID)。
例如,下面的类生成每个线程本地的唯一标识符。 线程的ID是在第一次调用ThreadId.get()时分配的,并且在以后的调用中保持不变。
只要线程是活动的并且ThreadLocal实例是可访问的,则每个线程都对其线程局部变量的副本持有隐式引用。 线程消失后,其线程本地实例的所有副本都将进行垃圾回收(除非存在对这些副本的其他引用)

案列

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<Integer> threadId =
           new ThreadLocal<Integer>() {
               @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();
       }
   }

属性

// 原子类  用于获取下一个 线程 hash 值  原子类-> 线程安全
private static AtomicInteger nextHashCode = new AtomicInteger();
// 线程 hash 增量步长 10 进制 : 1640531527  二进制: 1100001110010001000011001000111
// 将隐式顺序线程本地ID转换为用于2的幂次方表的近似最佳分布的乘法哈希值
private static final int HASH_INCREMENT = 0x61c88647;
private final int threadLocalHashCode = nextHashCode();

方法

// 获取下一个hashCode  使用原子类 增加  HASH_INCREMENT 
private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);}
// 返回 当前 线程 默认值 , 每个线程最多调用一次 但当调用remove和get后可以再次调用
protected T initialValue() {return null;}

// ThreadLocalMap 在 Thread 类中进行保存 
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

数据的操作委托给 ThreadLocalMap

ThreadLocalMap 主要用于数据存放

ThreadLocalMap是自定义的哈希映射,仅适用于维护线程局部值。 该类是包私有的,以允许声明Thread类中的字段。 为了帮助处理非常长的使用寿命,哈希表条目使用WeakReferences作为键。 但是,由于不使用参考队列,因此仅在表空间不足时,才保证删除过时的条目。

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
// 初始化大小 必须是 2 的幂次方
private static final int INITIAL_CAPACITY = 16;

// 保存数据表
private Entry[] table;
// 数据项
private int size = 0;
// 下次扩容 的阀值 默认0  --> 懒加载
private int threshold;
// 更新 扩容阀值
private void setThreshold(int len) {threshold = len * 2 / 3;}
// 下一个索引
private static int nextIndex(int i, int len) {return ((i + 1 < len) ? i + 1 : 0);}
// 前一个索引
private static int prevIndex(int i, int len) {return ((i - 1 >= 0) ? i - 1 : len - 1);}
// 带 数据的初始化方法
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    table = new Entry[INITIAL_CAPACITY];
    // hash 值  & 容量 - 1 
    int i = firstKey.threadLocalHashCode  & (INITIAL_CAPACITY - 1);
    table[i] = new Entry(firstKey, firstValue);
    size = 1;
    setThreshold(INITIAL_CAPACITY);
}
// 重新构造 一个 map
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++;
            }
        }
    }
}
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);
}
// key 不存在  
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 // 探测 hash冲突 进行探测
	        i = nextIndex(i, len);
	    e = tab[i];
	}
	return null;
}
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();
        // 同一个 thredLocal 就直接替换
        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();
}
// 删除 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;
        }
    }
}

 // 替换槽位值
private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                               int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    Entry e;
	// 找到前一个 槽位  这个槽位 未有数据   目的是 避免堆引用,GC回收
    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);
}
// 清空指定hash槽的值
private int expungeStaleEntry(int staleSlot) {
    Entry[] tab = table;
    int len = tab.length;
    // 清空当前槽
    tab[staleSlot].value = null;
    tab[staleSlot] = null;
    size--;

    // 重新hash 知道遇到null
    Entry e;
    int i;
    for (i = nextIndex(staleSlot, len);
         (e = tab[i]) != null;
         i = nextIndex(i, len)) {
        ThreadLocal<?> k = e.get();
        // 清空null 值
        if (k == null) {
            e.value = null;
            tab[i] = null;
            size--;
        } else {
            int h = k.threadLocalHashCode & (len - 1);
            if (h != i) {
                tab[i] = null;
                // 找到下一个hash槽位  原因 是  在放值的时候采用的hash碰撞进行存放的
                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;
 }
 
// 重新 hash
private void rehash() {
    expungeStaleEntries();

    // Use lower threshold for doubling to avoid hysteresis
    if (size >= threshold - threshold / 4)
        resize();
}
// 二倍扩容
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; // 置空 GC回收
            } else {
                int h = k.threadLocalHashCode & (newLen - 1);
               // 碰撞法 解决hash冲突
                while (newTab[h] != null)
                    h = nextIndex(h, newLen);
                newTab[h] = e;
                count++;
            }
        }
    }
    setThreshold(newLen);
    size = count;
    table = newTab;
}
// 清空 所有 槽位
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 实现线程数据共享,自定义Map的实现,采用的Hash碰撞方法进行数据存储,原子类进行hash分配。也将数据绑定在线程上 , 在线程退出时进行数据销毁。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值