多线程(46)线程局部存储

线程局部存储(Thread Local Storage, TLS)是一种允许数据在多个线程中被独立地存储的编程范式。在Java中,这通过ThreadLocal类实现,它提供了一种线程封闭的机制,确保每个线程都有自己的变量副本,从而避免了变量共享所带来的线程安全问题。

工作原理

ThreadLocal创建的变量,对于使用它的每个线程,都会提供一个独立初始化的副本,各个线程可以修改自己的副本而不会影响其他线程的副本。这种机制特别适合于实现线程安全的数据格式,或者保存线程的上下文信息,避免了同步操作的性能损耗。

实现机制

ThreadLocal内部通过维护一个ThreadLocal.ThreadLocalMap来实现线程局部存储,这是一个定制化的哈希映射,用于存储每个线程的局部变量。每个Thread对象都有一个ThreadLocalMap的引用,但这个映射表只能通过ThreadLocal对象访问。

ThreadLocalget()set(T value)方法被调用时,会先找到当前线程的ThreadLocalMap,然后根据ThreadLocal对象作为键在这个映射表中查找或修改对应线程的局部变量。

核心源码

考虑到详细的源码分析可能非常复杂,这里提供一个简化的视角来理解ThreadLocal的核心逻辑:

java 复制代码
public class ThreadLocal<T> {
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = t.threadLocals;
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                return (T)e.value;
            }
        }
        return setInitialValue();
    }

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = t.threadLocals;
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }
    
    T initialValue() {
        return null;
    }

    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = t.threadLocals;
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
        return value;
    }
    
    // 注意:实际源码中ThreadLocalMap的实现更加复杂,这里仅为了提供一个直观的印象。
    static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;
            
            Entry(ThreadLocal<?> k, Object value) {
                super(k);
                this.value = value;
            }
        }
        
        private Entry[] table;
        
        void set(ThreadLocal<?> key, Object value) {
            // 实现省略:在表中找到或创建与key关联的条目,并更新其值
        }
        
        Entry getEntry(ThreadLocal<?> key) {
            // 实现省略:根据key找到对应的条目
            return null;
        }
    }
    
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap();
        t.threadLocals.set(this, firstValue);
    }
}

代码演示

下面是一个使用ThreadLocal的基本示例:

java 复制代码
public class ThreadLocalExample {
    public static ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 1);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            final int threadId = i;
            new Thread(() -> {
                Integer value = threadLocalValue.get();
                System.out.println("Thread " + threadId + " initial value: " + value);
                
                // 修改本线程的threadLocalValue
                threadLocalValue.set(value + threadId);
                
                // 再次读取,显示已修改的值
                System.out.println("Thread " + threadId + " new value: " + threadLocalValue.get());
            }).start();
        }
    }
}

在这个例子中,每个线程都能独立地获取和设置threadLocalValue的值,而不会影响到其他线程。

注意事项

虽然ThreadLocal可以方便地实现线程局部存储,但它也可能导致内存泄漏问题。因为ThreadLocal.ThreadLocalMap中的键(ThreadLocal对象)是通过弱引用实现的,而值(存储的对象)是通过强引用实现的。如果一个ThreadLocal对象没有任何强引用指向它,那么在下一次垃圾回收时,这个ThreadLocal对象将被回收,但是它在ThreadLocalMap中对应的值不会被回收,这就可能导致内存泄漏。为了避免这种情况,一般建议在不再需要使用ThreadLocal变量时调用其remove()方法。

总之,ThreadLocal提供了一种优雅的线程局部存储方案,但需要谨慎使用,以避免内存泄漏等问题。

相关推荐
二闹8 分钟前
三个注解,到底该用哪一个?别再傻傻分不清了!
后端
用户490558160812520 分钟前
当控制面更新一条 ACL 规则时,如何更新给数据面
后端
林太白22 分钟前
Nuxt.js搭建一个官网如何简单
前端·javascript·后端
码事漫谈23 分钟前
VS Code 终端完全指南
后端
该用户已不存在1 小时前
OpenJDK、Temurin、GraalVM...到底该装哪个?
java·后端
怀刃1 小时前
内存监控对应解决方案
后端
码事漫谈1 小时前
VS Code Copilot 内联聊天与提示词技巧指南
后端
Moonbit2 小时前
MoonBit Perals Vol.06: MoonBit 与 LLVM 共舞 (上):编译前端实现
后端·算法·编程语言
Moonbit2 小时前
MoonBit Perals Vol.06: MoonBit 与 LLVM 共舞(下):llvm IR 代码生成
后端·程序员·代码规范
Moonbit2 小时前
MoonBit Pearls Vol.05: 函数式里的依赖注入:Reader Monad
后端·rust·编程语言