掘金 后端 ( ) • 2024-04-12 13:45

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

工作原理

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

实现机制

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

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

核心源码

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

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的基本示例:

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提供了一种优雅的线程局部存储方案,但需要谨慎使用,以避免内存泄漏等问题。