乐趣区

关于java:理解-ThreadLocal-类的特性

留神:调试 JDK 源代码,不能精确地查看变量,必须从新编译 rt.jar,具体方法点这里


先贴出 Java Doc 里对于 ThreadLocal 类的阐明:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
此类提供线程局部变量。这些变量与那些一般变量不同,因为拜访一个(通过其 get 或 set 办法)的每个线程都有本人独立初始化的变量正本。ThreadLocal 实例通常是公有动态字段,并心愿将状态与线程(例如,用户 ID 或事务 ID)相关联。

大家先剖析上面这段代码:

public class ThreadTest {public static void main(String[] args) {MyThread thread = new MyThread();
        thread.start();
        // thread.join();  // 3,省略异样解决语句
        System.out.println(thread.get());  // 1
    }
}

class MyThread extends Thread {private ThreadLocal<String> value = new ThreadLocal<>();
    public String get() {return value.get();
    }
    @Override
    public void run() {value.set("hello");  // 2
    }
}

这段代码的输入后果可能跟大家料想的不一样,后果不是 hello 而是 null。至于为什么,上面咱们通过剖析 Java 源代码和 Debug 查看变量来了解这一过程。

ThreadLocal.get() 办法如下:

public T get() {Thread t = Thread.currentThread();  // 留神这里
    ThreadLocalMap map = getMap(t);  // ThreadLocal.ThreadLocalMap
    if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);  // key 为以后 ThreadLocal 实例
        if (e != null) {@SuppressWarnings("unchecked")
            T result = (T) e.value;
            return result;
        }
    }
    return setInitialValue();}

由上可知,该办法先是获取以后执行的线程。每个线程都有一个 ThreadLocal.ThreadLocalMap 类型的名为 threadLocals 的变量,通过 ThreadLocal 保留的数据理论都寄存在这个变量里,get 数据的 Key 值是 this 指向的 ThreadLocal 实例。
上面是 ThreadLocal.set() 办法如下:

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

set() 办法也是首先获取以后正在执行的线程,再取得线程的 ThreaLocalMap 变量,再进行赋值。

咱们提到,第一段代码的执行后果是 null 而不是 hello。首先,程序启动时会产生两个线程,一个是 main 线程,执行 main() 办法,另一个才是咱们的 MyThread 实例,它的名字默认是 Thread-0。所以因为线程并行的缘故,代码 1 会先于代码 2 执行,所以 get() 先于 set() 就是后果为 null 起因?不,只是一部分起因。

咱们将代码 3 勾销正文,后果依然是 null。上面我通过 Debug 来展现这一过程。
因为 thread.join() 办法,代码先执行到代码 2,对 ThreadLocal 变量进行赋值。

进入 ThreaLocal.set() 办法,咱们能够看到以后线程 t 的是 Thread-0。也就是说
“hello” 这个值是保留在 MyThread 实例的 threadLocals 里的。

继续执行到代码 1。

进入 ThreadLocal.get() 办法,会发现以后线程 t 指向的线程却是 main 线程。

当初,咱们能够了解第一段代码的执行后果为什么是 null 而不是 hello 了。因为咱们在调用 set()办法赋值时,值是保留在 Thread-0 里的,而在 get() 取值又取的是 main 线程里的(因为是在 main() 办法里被调用),main 线程的 ThreadLocal 变量没有初始值也没有赋值,所以输入的后果就是 null。

所以这就是 Java Doc 提到的 ThreadLocal 的个性:

每个线程都有本人独立初始化的变量正本。

main()办法所在的 main 线程也不例外。

退出移动版