专业剑 : ThreadLocal

Threadlocal

ThreadLocal 详解_似寒若暖的博客-CSDN博客_threadlocal

Thread的局部变量,线程局部变量ThreadlocalValue。不同线程之间互不干扰。在线程的生命周期内起作用。

在并发场景中,成员变量是线程不安全的,因为多个线程在同时操作同一个变量。

ThreadLocal使得每个线程都拥有自己独立的变量,竞态条件被彻底消除了,在并发模式下是绝对安全的变量。


Threadlocal的底层实现

解决多线程间共享变量线程安全问题的大杀器——ThreadLocal_threadlocal怎么保证线程安全_YHJ的博客-CSDN博客

1在Thread类中定义了类型为ThreadLocalMap的变量ThreadLocals,是一个定制化的HashMap,初始值为null。

在ThreadLocal类中定义了内部静态类ThreadLocalMap,并且定义了get(), set(T), getMap()对其操作。

set(T value)
public void set(T value) {
    Thread t = Thread.currentThread(); // 1. 获取当前线程
    ThreadLocalMap map = getMap(t); // 2.以当前线程作为getMap方法的参数,返回当前线程的变量ThreadLocals。
    if (map != null)
        map.set(this, value); // 3. 把value存入当前线程的变量ThreadLocals,注意key就是当前ThreadLocal的实例对象引用。
    else
        createMap(t, value); // 如果是第一次调用set,则创建当前线程的变量ThreadLocals,再赋值。
}
get()
public T get() {
    Thread t = Thread.currentThread(); // 1. 获取当前线程
    ThreadLocalMap map = getMap(t); // 2.以当前线程作为参数和key,返回当前线程的变量ThreadLocals。
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this); // 3. 返回对应的本地变量的值。
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue(); // 如果当前线程的变量ThreadLocals为空,则初始化该变量。
}
getMap(Thread t)
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals; // 获取线程自己的变量threadLocals,其类型是ThreadLocalMap.
}
createMap
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
childValue
T childValue(T parentValue) {
    throw new UnsupportedOperationException();
}

相当于,ThreadLocal类管理了所有线程自己的ThreadLocalMap类。

ThreadLocal类提供了对这个ThreadLocalMap类的get和set方法。

而ThreadLocalMap类是通过一个对象数组实现的,Entry[] table


Thread线程类中定义了一个threadlocals成员变量,类型为ThreadLocalMap类(可以理解为定制的HashMap)。key为我们所定义的ThreadLocal变量的this引用,value则为set方法传递的值。

我们先定义一个ThreadLocal变量,该变量将来会存放到线程的threadlocals成员变量(可以理解为定制的HashMap)中。

该变量存放在哪个线程中,取决于是在哪个线程中调用了该变量的set()方法。

比如在线程A中调用了该变量的set()方法,就会把该变量存放到线程A的threadlocals成员变量中。其中key为该变量的this引用,value为该变量的值。

如果同时在线程B中调用了该变量的set()方法,则同样会把该变量存放到线程B的threadlocals成员变量中。其中key为该变量的this引用,value为该变量的值。

这可能是一种不安全的使用方式,因为两个线程的threadLocals中的key和value都一样,特别是value指向了同一个对象变量时,这就导致了线程不安全。

正确的做法是在线程内部定义对象变量。



使用Threadlocal的注意事项

出现内存溢出的场景

如果当前线程一直不消亡,那么这些本地变量会一直存在,造成内存溢出。所以需要在使用完毕后,使用remove()方法删除threadLocals中的本地变量。


线程不安全的场景1

注意:如果使用不当,ThreadLocal可能是线程不安全的。

ThreadLocal不安全的情况举例(附代码) - 坚守梦想 - 博客园 (cnblogs.com)

在这个例子中,两个线程的threadLocals中的key和value都一样,特别是value指向了同一个对象,这就导致了线程不安全。

正确的做法是key可以相同,但是value指向不同的对象。


InheritableThreadLocal

为了解决让子线程能够访问到父线程中的值的问题,lnheritableThreadLocal 应运而生。

lnheritableThreadLocal 继承自 ThreadLocal,并提供了一个新特性:让子线程可以访问在父线程中设置的本地变量值。

问题:在上图中, ThreadLocal 变量在父线程中被设置值后,在子线程中是获取不到的。

原因:在子线程里面调用get()方法时,Thread t = Thread.currentThread() 代码是获取当前线程。当前线程是子线程,而调用set()方法给threadLocal赋值的线程是main,两者是不同的线程。

故子线程调用get方法取得的threadLocal值为null,main线程调用get方法取得的threadLocal值为“hello world”。

解决方案:使用lnheritableThreadLocal类定义成员变量。

getMap()
ThreadLocalMap getMap(Thread t) {
   return t.inheritableThreadLocals; // 获取当前线程的inheritableThreadLocals变量的实例,而不再是threadLocals。
}
createMap
void createMap(Thread t, T firstValue) {
    t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); // 当第一次调用 InheritableThreadLocal 实例的set方法时,创建的是当前线程的inheritableThreadLocals变量的实例而不再是threadLocals了。
}
childValue
protected T childValue(T parentValue) {
    return parentValue;
}

InheritableThreadLocal实现子线程可以访问父线程的线程变量的实现原理如下

1.InheritableThreadLocal类通过重写createMap 和 getMap 方法,让本地变量保存到了当前线程的inheritableThreadLocals变量中。

2.当线程A调用inheritableThreadLocal实例的set()get()方法时,就会创建当前线程A的inheritableThreadLocals变量。

3.当父线程A创建子线程B时,构造函数会把父线程A中的inheritableThreadLocals变量里面的本地变量值复制一份保存到子线程B的inheritableThreadLocals变量里。


使用InheritableThreadlocal的注意事项

注意:如果使用不当,InheritableThreadLocal可能是线程不安全的。

Typically, though, you should not be using InheritableThreadLocal objects on anything that can change because it is not thread-safe.

线程不安全的场景1

问题:当复制时,是否会出现父线程和子线程的inheritableThreadLocals变量的value指向同一个对象?会。

不仅是父子线程的inheritableThreadLocals变量的value可能指向同一个对象,同一个父线程的多个子线程的inheritableThreadLocals变量的value可能指向同一个对象。

这就导致了线程不安全。

线程不安全的场景2

还有一种场景,导致线程不安全。

线程池使用InheritableThreadLocal出现数据脏乱分析和解决方案 | 易学站 (dzscc.com)

上面的例子的问题在于用了线程池。首先,Thread –> ThreadLocalMap–>Entry–>Value,这条引用链导致Entry不会回收,Value也不会回收;

其次是父线程A是核心线程,生命周期很长。当且只当子线程B第一次从线程池获取,第一次创建子线程B时,子线程B会从主线程A获取inheritableThreadLocals值。

当子线程B第二次从线程池获取,就不再有子线程B从主线程A获取inheritableThreadLocals值这个过程。

当线程池中的线程全部被获取过后,结果导致核心父线程A的inheritableThreadLocals数据会更新,但是所有的子线程数据都一直存放着老数据



Attachments: