网站开发实训周报,苏州网站建设 牛,内蒙古创意星空网站开发,做网站阜新文章目录 ThreadLocalThreadLocal 的基本原理ThreadLocal 的实现细节内存泄漏源码使用场景 ConcurrentHashMap 怎么实现线程安全的CAS初始化源码添加元素putVal方法 ThreadLocal
ThreadLocal 是 Java 中的一种用于在多线程环境下存储线程局部变量的机制#xff0c;它可以为每… 文章目录 ThreadLocalThreadLocal 的基本原理ThreadLocal 的实现细节内存泄漏源码使用场景 ConcurrentHashMap 怎么实现线程安全的CAS初始化源码添加元素putVal方法 ThreadLocal
ThreadLocal 是 Java 中的一种用于在多线程环境下存储线程局部变量的机制它可以为每个线程提供独立的变量副本从而避免多个线程之间的竞争条件。ThreadLocal 在实际应用中特别是在需要在线程间共享资源的场景下发挥着重要作用。
ThreadLocal 的基本原理
ThreadLocal 的核心概念是为每个线程维护一个独立的变量副本。当一个线程通过 ThreadLocal 访问某个变量时实际上访问的是属于该线程的独立副本。ThreadLocal 通过以下几个关键点实现了这一点
每个线程持有自己的 ThreadLocalMap 每个线程内部都有一个 ThreadLocalMap 对象这个对象存储了 ThreadLocal 变量及其对应的值。ThreadLocalMap 是 Thread 类中的一个成员变量因此它与线程的生命周期绑定。ThreadLocalMap** 的结构** ThreadLocalMap 是一个定制的哈希表它的键是 ThreadLocal 对象而值是对应的线程局部变量的值。每个线程持有的 ThreadLocalMap 可以存储多个 ThreadLocal 变量。变量的存取过程 当线程调用 ThreadLocal 的 get() 方法时ThreadLocal 会获取当前线程持有的 ThreadLocalMap然后通过自身作为键从 ThreadLocalMap 中获取变量的值。当线程调用 ThreadLocal 的 set() 方法时ThreadLocal 会将变量的值存储到当前线程持有的 ThreadLocalMap 中。
ThreadLocal 的实现细节
ThreadLocal类 ThreadLocal 本身只是提供了一套访问接口它内部依赖于 ThreadLocalMap 来存储和获取线程局部变量。ThreadLocalMap的实现 ThreadLocalMap 是一个内部类它的结构类似于一个简化的哈希表。ThreadLocalMap 使用了一个简单的开放地址法来处理哈希冲突。每个键值对的键是一个 ThreadLocal 的弱引用WeakReference这有助于避免内存泄漏当 ThreadLocal 对象被回收后键会变成 null相应的值也会被清理。 ThreadLocalMap的垃圾回收 由于 ThreadLocalMap 使用了弱引用ThreadLocal 对象不会阻止其被垃圾回收机制回收。当 ThreadLocal 对象被回收后ThreadLocalMap 中对应的键会变成 null但是值仍然会存在。这种情况下如果不及时清理可能会导致内存泄漏。remove()方法 ThreadLocal 提供了一个 remove() 方法可以显式地将当前线程持有的 ThreadLocal 变量移除。这有助于防止内存泄漏特别是在使用线程池的场景下线程会被重复利用如果不清理可能会导致数据污染或内存泄漏。
内存泄漏
实际上 ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用弱引用的特点是如果这个对象只存在弱引用那么在下一次垃圾回收的时候必然会被清理掉。 所以如果 ThreadLocal 没有被外部强引用的情况下在垃圾回收的时候会被清理掉的这样一来 ThreadLocalMap中使用这个 ThreadLocal 的 key 也会被清理掉。但是value 是强引用不会被清理这样一来就会出现 key 为 null 的 value。 ThreadLocal其实是与线程绑定的一个变量如此就会出现一个问题如果没有将ThreadLocal内的变量删除remove或替换它的生命周期将会与线程共存。通常线程池中对线程管理都是采用线程复用的方法在线程池中线程很难结束甚至于永远不会结束这将意味着线程持续的时间将不可预测甚至与JVM的生命周期一致。
如何避免内存泄漏
每次使用完threadlocal调用remove方法清除尽可能把threadlocal变量定义为static final这样可以避免频繁创建实例。内部优化 调用set)方法会采用采样清理全量清理扩容时还能继续检查调用get方法如果没有命中向后环形查找时进行清理。调用remove方法清理当前entry还会向后清理
最好是使用完之后手动调用remove方法这个方法底层会调用map的remove将 Entry 移除。 因此ThreadLocal内存泄漏的根源是由于ThreadLocalMap的生命周期跟Thread一样长如果没有手动删除对应key就会导致内存泄漏而不是因为弱引用。弱引用只是保证了 ThreadLocal 会被 GC 自动回收。 最佳做法每次使用完ThreadLocal都调用它的remove()方法清除数据。
源码
static class ThreadLocalMap {static class Entry extends WeakReferenceThreadLocal? {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal? k, Object v) {super(k);value v;}}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);}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);}
}该Java静态内部类Entry继承自WeakReferenceThreadLocal?主要用于存储ThreadLocal与其关联对象的弱引用及其对应的值。具体说明如下 value存储与ThreadLocal关联的具体值。 构造方法接收一个ThreadLocal对象和一个值创建一个弱引用来持有ThreadLocal对象并保存关联值。
使用场景
ThreadLocal 常用于以下场景
数据库连接管理每个线程持有一个独立的数据库连接避免多个线程同时使用同一个连接。用户上下文信息在 Web 应用中每个线程处理一个用户请求可以通过 ThreadLocal 存储和访问该用户的上下文信息。线程安全的对象实例通过 ThreadLocal 为每个线程创建独立的对象实例避免线程间的竞争条件。 注意事项内存泄漏如果 ThreadLocal 变量不及时清理可能会导致内存泄漏尤其是在使用线程池时要特别注意调用 remove() 方法。适用场景ThreadLocal 适合用于线程独立的数据存储不适合跨线程的数据共享。 总结来说ThreadLocal 是一种通过在每个线程中创建独立变量副本的方式来实现线程隔离的工具它的底层依赖于每个线程持有的 ThreadLocalMap 来存储这些变量副本从而确保线程间的数据独立性和安全性。
例子 ThreadLocalMap 中的 Key 和 Value
Key 在 ThreadLocalMap 中Key 是 ThreadLocal 实例本身。ThreadLocal 对象作为键指向当前线程所持有的变量。 Value Value 是由 ThreadLocal 对象所关联的值。在本例中Value 是 Integer存储的是每个线程独立的计数器值。
每个线程都有一个 ThreadLocalMap这个 ThreadLocalMap 使用 ThreadLocal 实例作为键来存储和获取对应的线程局部变量值从而实现数据隔离。
public class MultipleThreadLocalExample {// 定义两个不同的 ThreadLocal 变量private static ThreadLocalInteger threadLocalCounter1 ThreadLocal.withInitial(() - 0);private static ThreadLocalString threadLocalCounter2 ThreadLocal.withInitial(() - Initial Value);public static void main(String[] args) {// 启动一个线程独立操作两个 ThreadLocal 变量Thread thread new Thread(() - {// 操作第一个 ThreadLocal 变量threadLocalCounter1.set(threadLocalCounter1.get() 10);System.out.println(Thread.currentThread().getName() - Counter1: threadLocalCounter1.get());// 操作第二个 ThreadLocal 变量threadLocalCounter2.set(threadLocalCounter2.get() Updated);System.out.println(Thread.currentThread().getName() - Counter2: threadLocalCounter2.get());});thread.start();}
}多个 ThreadLocal 变量在同一线程中的工作机制
ThreadLocalMap 的键区分 在同一个线程中不同的 ThreadLocal 变量对应着不同的键。这些键就是 ThreadLocal 对象本身。因此即使在同一个线程中每个 ThreadLocal 实例都能独立存储和访问自己的值不会与其他 ThreadLocal 变量发生冲突。 ThreadLocalMap 的存储结构 ThreadLocalMap 通过一个哈希表来存储键值对。键是 ThreadLocal 对象值是线程局部变量的实际数据。因此同一线程中的多个 ThreadLocal 变量不会相互覆盖或混淆。
ConcurrentHashMap 怎么实现线程安全的
采用了CAS算法compareAndSwapObject和部分代码使用synchronized锁保证线程安全。 对应的非并发容器:HashMap 目标:代替Hashtable、synchronizedMap支持复合操作。 原理:JDK6中采用一种更加细粒度的加锁机制 Segment “分段锁”JDK8中采用 volatile CAS 或者 synchronized 。 **添加元素时首先会判断容器是否为空
如果为空则使用 volatile 加 CAS 来初始化如果容器不为空则根据存储的元素计算该位置是否为空。 如果根据存储的元素计算结果为空则利用 CAS 设置该节点如果根据存储的元素计算结果不为空则使用 synchronized 然后遍历桶中的数据并替换或新增节点到桶中最后再判断是否需要转为红黑树这样就能保证并发访问时的线程安全了**。
如果把上面的执行用一句话归纳的话就相当于是ConcurrentHashMap通过对头结点加锁来保证线程安全的锁的粒度相比 Segment 来说更小了发生冲突和加锁的频率降低了并发操作的性能就提高了。 而且 JDK 1.8 使用的是红黑树优化了之前的固定链表那么当数据量比较大的时候查询性能也得到了很大的提升从之前的 O(n) 优化到了 O(logn) 的时间复杂度。
CAS初始化源码
这段 Java 代码定义了一个名为 sizeCtl 的私有变量其类型为 int并且被 transient 和 volatile 修饰符所修饰。这个变量在类中的作用如下 Table 初始化和调整大小控制
当 sizeCtl 的值为负数时表示当前正在进行表的初始化或调整大小操作。 -1 表示正在初始化表。其他负数值如 -2, -3 等表示正在进行调整大小的操作并且 -1 减去该值即为当前活跃的调整大小线程的数量。 初始表大小或默认值 如果表table为空并且 sizeCtl 的值为非负数则该值表示创建表时应使用的初始大小。如果 sizeCtl 的值为 0则表示使用默认大小创建表。 调整大小的阈值在初始化之后sizeCtl 的值表示下次应该调整表大小时元素的数量阈值。 变量修饰符说明transient表示这个变量不会被序列化。当对象被序列化成字节流时sizeCtl 的值不会被保存。volatile确保多线程环境下的可见性和有序性即任何线程对 sizeCtl 的修改都会立即反映到其他线程中。 通过这种方式sizeCtl 变量帮助实现了并发哈希表如 ConcurrentHashMap在初始化和动态调整大小过程中的控制逻辑。
private transient volatile int sizeCtl;
private final NodeK,V[] initTable() {NodeK,V[] tab; int sc;while ((tab table) null || tab.length 0) {if ((sc sizeCtl) 0)Thread.yield(); // lost initialization race; just spin提示调度器当前线程愿意放弃处理器使用权else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {try {if ((tab table) null || tab.length 0) {int n (sc 0) ? sc : DEFAULT_CAPACITY;SuppressWarnings(unchecked)NodeK,V[] nt (NodeK,V[])new Node?,?[n];table tab nt;sc n - (n 2);}} finally {sizeCtl sc;}break;}}return tab;
}该函数初始化哈希表主要功能如下
检查当前表格是否为空或长度为零。使用CAS操作安全地初始化表格数组。如果成功则根据sizeCtl记录的大小创建新数组并更新sizeCtl值。
添加元素putVal方法 /*** 核心方法用于处理put和putIfAbsent操作* 该方法实现了哈希表的插入逻辑包括处理哈希冲突和数据结构转换链表转红黑树* * param key 键不能为null* param value 值不能为null* param onlyIfAbsent 如果为true则仅在键不存在时进行插入* return 插入前该键对应的旧值如果没找到则返回null*/final V putVal(K key, V value, boolean onlyIfAbsent) {// 检查键值对是否为null为null则抛出异常if (key null || value null) throw new NullPointerException();// 扩散哈希码以减少哈希冲突int hash spread(key.hashCode());int binCount 0; // 用于记录链表或红黑树中的元素数量// 循环尝试在哈希表中插入值for (NodeK,V[] tab table;;) {NodeK,V first; int n, i, firstHash;// 表为空时初始化哈希表volatile和CASif (tab null || (n tab.length) 0)tab initTable();// 位置i处的节点为空直接插入新节点else if ((first tabAt(tab, i (n - 1) hash)) null) {// 存储的元素在该node数组的下标位置结果为空则利用 CAS 设置该节点if (casTabAt(tab, i, null,new NodeK,V(hash, key, value, null)))break; // 成功插入跳出循环}// 位置i处的节点处于迁移中帮助完成迁移else if ((firstHash first.hash) MOVED)tab helpTransfer(tab, first);// 位置i处的节点正常进行插入或更新操作else {V oldVal null;// 同步锁确保线程安全。存储的元素计算结果不为空则使用 synchronizedsynchronized (first) {// 再次检查节点防止并发修改if (tabAt(tab, i) first) {// 链表形式遍历链表找到键或插入新节点if (firstHash 0) {binCount 1;for (NodeK,V e first;; binCount) {K ek;// 找到匹配的键更新值并跳出循环if (e.hash hash ((ek e.key) key ||(ek ! null key.equals(ek)))) {oldVal e.val;if (!onlyIfAbsent)e.val value;break;}NodeK,V pred e;// 链表末尾插入新节点if ((e e.next) null) {pred.next new NodeK,V(hash, key,value, null);break;}}}// 红黑树形式调用红黑树的插入或更新方法else if (first instanceof TreeBin) {NodeK,V p;binCount 2;if ((p ((TreeBinK,V)first).putTreeVal(hash, key,value)) ! null) {oldVal p.val;if (!onlyIfAbsent)p.val value;}}}}// 链表节点数超过阈值转换为红黑树if (binCount ! 0) {if (binCount TREEIFY_THRESHOLD)treeifyBin(tab, i);// 找到旧值返回旧值if (oldVal ! null)return oldVal;break;}}}// 更新哈希表大小和修改次数addCount(1L, binCount);return null; // 未找到旧值返回null}