java.lang.ThreadLocal实现原理分析

版权声明:转载请注明出处 https://blog.csdn.net/abc123lzf/article/details/81978210

一、简介

ThreadLocal隶属java.lang包,表示线程私有变量,也可叫做线程本地变量。它为单个线程单独创立了一个副本,每个线程只可访问属于自己的变量,不可访问和修改别的线程所属的变量。
ThreadLocal属于一个泛型类,泛型参数为变量的类型,可以通过重写initialValue方法来实现对该变量初始值的设置。
ThreadLocal提供了以下API实现对变量的控制:

public T get(); //返回该变量
public void set(T value); //设置该变量
public void remove(); //移除该变量

例如下面的代码:

public class Test {

    private static ThreadLocal<Integer> tl = new ThreadLocal<Integer>() {
        //重写设置初始值的方法
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public static void main(String[] args) {
        System.out.println(tl.get());
        tl.set(tl.get() + 1);
        System.out.println(tl.get());

        Thread t = new Thread() {
            public void run() {
                System.out.println(tl.get());
            }
        };

        t.start();
    }
}

输出为:
0
1
0
看到这里想必大家理解了ThreadLocal的使用方法及其作用了吧。

二、源码分析

ThreadLocal的主要方法有:

public T get(); //返回该变量
public void set(T value); //设置该变量
public void remove(); //移除该变量
protected T initialValue(); //返回变量的初始值
(1)initialValue方法

initialValue方法的作用是为变量设置初始值,该方法的默认实现是:

protected T initialValue() {
    return null;
}

如果想要为该变量设置一个初始值,只需重写该方法即可,例如:

@Override
protected String initialValue() {
    return "hello world";
}

ThreadLocal对象中很多方法的实现会调用该方法来设置初始值。

(2)get方法分析

想要了解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) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

首先是获取当前运行线程对象,然后根据该线程对象找到对应的ThreadLocalMap(ThreadLocalMap是ThreadLocal的一个default访问权限内部类,其作用我们稍后分析)。如果找到了该线程对应的ThreadLocalMap,则通过当前ThreadLocal对象作为键查找Map中对应的Entry(键值对)对象,如果找到了,则返回Entry对象的value。否则调用setInitialValue方法将当前ThreadLocal对象(this)和变量作为键值对存入ThreadLocalMap并返回变量。

getMap方法:

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

该方法很简单,即返回Thread对象的成员变量threadLocals。
查看java.lang.Thread对象源码:

ThreadLocal.ThreadLocalMap threadLocals = null;

相信很多人看到这里已经大致明白:每个Thread对象会持有一个ThreadLocalMap,当我们创建一个ThreadLocal对象时,该ThreadLocal对象本身作为键,变量作为值存入该ThreadLocalMap中,来实现线程的私有变量。

下面再来看setInitialValue方法的实现:

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

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

该方法作用是将ThreadLocal对象存入当前线程对象的ThreadLocalMap中,最后返回变量初始值。
该方法首先会调用initialValue方法初始化变量,然后获取当前Thread对象所属的ThreadLocalMap(即成员变量threadLocals),如果Thread对象成员变量threadLocals不为空,则将当前ThreadLocal对象及其变量存入Map,否则会调用createMap方法为当前Thread对象新建一个ThreadLocalMap并存入当前ThreadLocal对象及其变量。

(3)ThreadLocal内部类:ThreadLocalMap

ThreadLocalMap负责存储ThreadLocal及其变量,即ThreadLocal对象本身作为键,ThreadLocal存储的变量作为值。每个Thread对象在声明一个ThreadLocal后会持有一个ThreadLocalMap的引用,来实现ThreadLocal的功能。

ThreadLocalMap持有一个内部类Entry,类似于HashMap.Node类,负责保存键值对。

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

Entry继承了WeakReference类,使Entry的键为弱引用。

看到这里很多人或许有这样一个疑问:为什么Entry要继承WeakReference?
既然将ThreadLocal声明为弱引用,那么自然会联想到和GC有关。

如果不声明为弱引用,以最上面Test类的代码为例,当我们将上述ThreadLocal类型的静态变量tl设置为null时,Thread对象成员变量threadLocals依然保留有一个ThreadLocalMap,该Map中持有保存该ThreadLocal的Entry,在这个线程运行期间无法GC,从而引发内存泄漏。所以,Entry的键要声明为弱引用。
这里写图片描述
当我们弃用一个ThreadLocal对象时,最好通过调用其remove方法而不是直接设为null。

当向ThreadLocalMap添加一个ThreadLocal时,会获取ThreadLocal对象的threadLocalHashCode获取HashCode添加至Map中而不是调用hashCode()方法。
ThreadLocalMap持有一个Entry数组,采用线性探测法处理哈希冲突,而不是HashMap的拉链法。
为了尽量避免哈希冲突,ThreadLocal提供了hashCode的计算代码:

private static AtomicInteger nextHashCode = new AtomicInteger();

/**
* The difference between successively generated hash codes - turns
* implicit sequential thread-local IDs into near-optimally spread
* multiplicative hash values for power-of-two-sized tables.
*/
private static final int HASH_INCREMENT = 0x61c88647;

/**
* Returns the next hash code.
*/
private static int nextHashCode() {
    return nextHashCode.getAndAdd(HASH_INCREMENT);
}

每当新建一个ThreadLocal对象,会调用nextHashCode获取自己的hashCode
关于魔数0x61c88647,有兴趣的可以百度,这里不再赘述。

(4)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方法类似,set方法首先会获取当前运行的Thread对象并通过该对象获取对应的ThreadLocalMap,如果map为空,则为当前Thread对象新建一个ThreadLocalMap,否则直接将value放入map中。

(5)remove方法
public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

同样,获取当前Thread对应的ThreadLocalMap,然后调用ThreadLocalMap的remove方法移除ThreadLocal对象,无需通过弱引用机制对该ThreadLocal对象进行GC。

如果本文有错误或对文章有疑问,欢迎在评论区评论。

猜你喜欢

转载自blog.csdn.net/abc123lzf/article/details/81978210