ThreadLocal源码分析

ThreadLocal可用于提供线程私有的对象。

ThreadLocal对象经常被实现为私有的静态字段,用于获取与线程关联的某些状态变量。其常见用法如下

public class ThreadId {
    
    private static final AtomicInteger nextId = new AtomicInteger();
    
    private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return nextId.getAndIncrement();
        }
    }
    
    public static int get() {
        return threadId.get();
    }
}

在如上的代码中,在不同的线程下,我们使用get方法将会获取到不同的值。

如果我们自己来实现一个简单的ThreadLocal的话,可能会这么来写。

public class MyThreadLocal<T> {
    
    Map<Thread, T> map = new ConcurrentHashMap<>();
    
    T get() {
        Thread t = Thread.currentThread;
        if (!map.containsKey(t)) {
            T initialValue = initialValue();
            map.put(t, initialValue);
            return initialValue;
        } else {
            return map.get(t);
        }
    }
    
    void set(T t) {
        locals.put(Thread.currentThread(), t);
    }
}

在上面的我们的简单实现中,在MyThreadLocal类中构造了一个map对象来存储线程和变量的关联关系。

但实际上,jdk的ThreadLocal实现上比上面这个简单的版本要复杂的多,同时也要优秀的多。

下面我们就来看一下其实现。

private final int threadLocalHashCode = nextHashCode();

private static AtomicInteger nextHashCode = new AtomicInteger();

private static final int HASH_INCREMENT = 0x61c8847;

private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

以上的代码的作用只有一个,就是用于生成一个哈希值,作为标识一个线程独有的key。HASH_INCREMENT是两次生成hashcode时候的差值,即如果连续两次来调用nextHashCode生成的结果值,则会相差HASH_INCREMENT。同时,使用了AtomicInteger,保证了在生成哈希码的时候的唯一性。

下面,先看一下ThreadLocal的get操作。

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            return (T)e.value;
        }
    }
    return setInitialValue();
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

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

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

在get操作中,我们就已经发现ThreadLocal的真实实现距离我们的设想已经越来越远了。在get操作中,我们并没有发现想象中的存储在ThreadLocal中的map,而是在getMap中获取了一个变量,叫做t.threadLocals。

我们查看Thread的代码,可以发现threadLocals的定义如下

ThreadLocal.ThreadLocalMap threadLocals = null;

threadLocals为线程类Thread中定义的一个变量,而变量的类型为ThreadLocal的一个内部类ThreadLocalMap。

ThreadLocalMap是一个定制化的Map类,是专门用于维护线程本地变量的,它所有的方法都只能在ThreadLocal类内部使用。我们可以发现,ThreadLocalMap存储的键值对的键的类型为ThreadLocal。想明白这一点,事情会简单很多。

和我们设想的不一样,ThreadLocal的实现中并没有构造一个map来维护线程和变量的关系,而是将这种关系保存在Thread类中。在我们调用get方法的时候,会首先获取到当前线程的threadLocals变量,然后通过这个变量来获取与线程关联的变量,获取的方式是

threadLocals.get(this); //其中this的类型为ThreadLocal

这样处理,直观上好像比我们的设想要绕一些,但jdk选择这种实现方式的出发点和考量是什么呢?

换种角度来思考,在jdk的实现方式中,ThreadLocal中并没有持有线程的任何数据,只是作为一个代理类来存在,而线程的数据仍然是存储在线程中。从数据的所有者角度来思考,jdk的实现方式更加的合理。

另外,还有一个深层次的考量,那就是避免内存泄漏的问题。如果按照我们的实现,在ThreadLocal中持有了Thread,而ThreadLocal的生命周期一般情况下都会比Thread要长,那么只要ThreadLocal还存在,对Thread的持有就不会释放,thread占用的内存就不会被回收,即使thread已经进入了终止状态,这样容易造成内存泄漏的发生。

在ThreadLocalMap的实现中,对Entry的定义如下

static class Entry extends WeakReference<ThreadLocal> {
    Object value;
    
    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}

可以看到Entry对ThreadLocal是弱引用,只要Thread可以被回收,那么ThreadLocalMap就能被回收掉。

Comments
Write a Comment