java:线程本地变量ThreadLocal

基本概念

线程本地变量是说,每个线程都有同一个变量的独有拷贝,它们访问的虽然是同一个变量,但每个线程都有自己的独立的值,这就是线程本地变量的含义。

使用场景

DateFormat/SimpleDateFormat

ThreadLocal是实现线程安全的一种方案,比如对于DateFormat/SimpleDateFormat,日期和时间操作是非线程安全的,实现安全的一种方式是使用锁,另一种方式是每次都创建一个新的对象,更好的方式就是使用ThreadLocal,每个线程使用自己的DateFormat,就不存在安全问题了,在线程的整个使用过程中,只需要创建一次,又避免了频繁创建的开销。

ThreadLocalRandom

即使对象是线程安全的,使用ThreadLocal也可以减少竞争,比如Random是线程安全的,但如果并发访问竞争激烈的话,性能会下降,所以Java并发包提供了类ThreadLocalRandom,它是Random的子类,利用了ThreadLocal,它没有public的构造方法,通过静态方法current获取对象。

上下文信息

ThreadLocal的典型用途是提供上下文信息,比如在一个Web服务器中,一个线程执行用户的请求,在执行过程中,很多代码都会访问一些共同的信息,比如请求信息、用户身份信息、数据库连接、当前事务等,它们是线程执行过程中的全局信息,如果作为参数在不同代码间传递,代码会很啰嗦,这时,使用ThreadLocal就很方便,所以它被用于各种框架如Spring中。

实现原理

ThreadLocal是怎么实现的呢?为什么对同一个对象的get/set,每个线程都能有自己独立的值呢?我们直接来看代码。

set方法的代码为:

1
2
3
4
5
6
7
8
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

它调用了getMap,getMap的代码为:

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

返回线程的实例变量threadLocals,它的初始值为null,在null时,set调用createMap初始化,代码为:

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

从以上代码可以看出,每个线程都有一个Map,类型为ThreadLocalMap,调用set实际上是在线程自己的Map里设置了一个条目,键为当前的ThreadLocal对象,值为value。ThreadLocalMap是一个内部类,它是专门用于ThreadLocal的,与一般的Map不同,它的键类型为WeakReference<ThreadLocal>,我们没有提过WeakReference,它与Java的垃圾回收机制有关,使用它,便于回收内存,具体我们就不探讨了。

get方法的代码为:

1
2
3
4
5
6
7
8
9
10
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();
}

通过线程访问到Map,以ThreadLocal对象为键从Map中获取到条目,取其value,如果Map中没有,调用setInitialValue,其代码为:

1
2
3
4
5
6
7
8
9
10
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;
}

initialValue()就是之前提到的提供初始值的方法,默认实现就是返回null。

remove方法的代码也很直接,如下所示:

1
2
3
4
5
public void remove() {
   ThreadLocalMap m = getMap(Thread.currentThread());
   if (m != null)
       m.remove(this);
}

简单总结下,每个线程都有一个Map,对于每个ThreadLocal对象,调用其get/set实际上就是以ThreadLocal对象为键读写当前线程的Map,这样,就实现了每个线程都有自己的独立拷贝的效果。

小结

简单总结来说:

  • ThreadLocal使得每个线程对同一个变量有自己的独立拷贝,是实现线程安全、减少竞争的一种方案。
  • ThreadLocal经常用于存储上下文信息,避免在不同代码间来回传递,简化代码。
  • 每个线程都有一个Map,调用ThreadLocal对象的get/set实际就是以ThreadLocal对象为键读写当前线程的该Map。

  • 在线程池中使用ThreadLocal,需要注意,确保初始值是符合期望的。

原文链接:https://juejin.im/post/58ff5a39570c350058f43db1

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页