ThreadLocal详解

261 阅读3分钟

一、ThreadLocal是什么

ThreadLocal属于java.lang包,是一个泛型类的线程本地变量,在每一个线程中会创建一个实例的副本,通常用private static修饰ThreadLocal变量。使用场景是:变量需要在线程间隔离但是在方法或类间共享。使用方法:声明成员变量

private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
// 方法中使用threadLocal.set(params);

二、源码解析

ThreadLocal类的内部维护了一个ThreadLocalMap的静态内部类,使用ThreadLocal的set和get方法时,实际使用的是ThreadLocalMap的set和get方法。具体看源码:

set方法:

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

get方法:

public T get() {   
        Thread t = Thread.currentThread();   
        ThreadLocalMap map = getMap(t);   
        if (map != null)   
            return (T)map.get(this);   

        // Maps are constructed lazily.  if the map for this thread   
        // doesn't exist, create it, with this ThreadLocal and its   
        // initial value as its only entry.   
        T value = initialValue();   
        createMap(t, value);   
        return value;   
    }

remove方法:

   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) {
                //清空key
                e.clear();
                //清空value
                expungeStaleEntry(i);
                return;
            }
        }
    }

ThreadLocalMap中的Entry是ThreadLocalMap中的一个静态内部类,且继承了弱引用。下面解释一下强引用、软引用、弱引用和虚引用的区别。

  • 强引用**(StrongReference)**:强引用是使用最普遍的引用。如果一个对象具有强引用,那垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机抛出OutOfMemoryError错误,使程序异常终止。

  • 软引用**(SoftReference)**:内存空间足够,垃圾回收器不会回收它;如果内存空间不足,就会回收这些对象的内存。软引用可用来实现内存敏感的高速缓存。软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收器回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。

  • 弱引用**(WeakReference):**只具有弱引用的对象拥有更短暂的生命周期。在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。不过,由于垃圾回收器是一个优先级很低的线程,因此不一定会很快发现那些只具有弱引用的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。

  • 虚引用**(PhantomReference)**: 虚引用主要用来跟踪对象被垃圾回收器回收的活动。虚引用必须和引用队列 (ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之 关联的引用队列中。

    static class Entry extends WeakReference<ThreadLocal> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal k, Object v) {
    super(k);
    value = v;
    } }

三、使用ThreadLocal的常见问题

1.内存泄漏

由源码可知,Entry中的k为ThreadLocal的弱引用,下一次垃圾回收时会被清理掉,但是value是强引用,不会被清理掉,就会出现key为null但是value有值的情况。如果没有显式的调用remove方法,线程执行完后会,通过ThreadLocal对象持有的对象是不会被释放的。

2.脏数据

线程复用会产生脏数据。线程池会复用Thread对象,在Thread中声明的静态属性ThreadLocal.ThreadLocalMap也会被重用。如果在实现线程run()方法中不显式地调用remove() 清理ThreadLocal信息,且下一个线程不调用set() 设置初始值,就可能get() 之前的线程信息,包括 ThreadLocal所关联的线程对象的value值。

Thread类中的声明:

ThreadLocal.ThreadLocalMap threadLocals = null;

解决方法:尽量在try-finally块中调用remove方法回收。

四、ThreadLocal使用实例

运行结果:可知线程间共享一个变量,但是互不影响,这是因为ThreadLocal为每一个线程提供了单独的副本。