ThreadLocal是什么
ThreadLocal这个词如果直接翻译就是“本地线程”,可是如果真的按“本地线程”来理解,那就确实大错特错了,ThreadLocal它并不是一个Thread,它跟Thread确实有关系,是用来维护Thread的有关变量的,把它命名为ThreadLocalVariable可能更容易让人理解,在多线程中ThreadLocal为变量在每个线程中都创建了一个跟特定线程有关的变量的副本,这样就可以使每个线程在运行中只可以使用与自己线程有关的特定的副本变量,而不会影响其它线程的副本变量,保证了线程间变量的隔离性。
对于线程来说,当多个线程都用到变量时,通过ThreadLocal使每个线程都有一个本地的独属于自己的变量,这也是类名中“Local”所要表达的意思。
ThreadLocal例子
ThreadLocal内部其实是一个map集合,key是各自的线程,value是我们要放入的对象。我们先通过一个ThreadLocal的简单demo先来理解一下ThreadLocal。
先来看MyThreadScopeData实体类,是对对象类型的数据封装,让外界不可直接操作ThreadLocal变量,我们通过单例模式得到这个实体类,这样的话让这个类针对不同线程分别创建一个独立的实例对象,然后将这个实例对象作为变量放到ThreadLocal中。
- package com.tgb.threadlocal;
- /**
- * 与ThreadLocal有关的实体对象
- * @author kang
- *
- */
- public class MyThreadScopeData {
- //声明一个ThreacLoacl
- private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();
- //通过单例模式来获取对象实例
- private MyThreadScopeData(){}
- public static MyThreadScopeData getThreadInstance(){
- MyThreadScopeData instance=map.get();
- if (instance ==null) {
- instance = new MyThreadScopeData();
- map.set(instance);
- }
- return instance;
- }
- private String name;
- private int age;
- public static ThreadLocal<MyThreadScopeData> getMap() {
- return map;
- }
- public static void setMap(ThreadLocal<MyThreadScopeData> map) {
- MyThreadScopeData.map = map;
- }
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- }
接下来是客户端调用类,我们在客户端代码中通过制造了两个线程,在每个线程中都生成一个随机数,然后把这个随机数放入到两个不同的threadlocal对象中,然后将数据从threadlocal中取出,打印出线程名称和取出来的数据;除了client客户端还包含了两个内部类,是用来从threadlocal对象中取出线程名称和数据并打印的
- package com.tgb.threadlocal;
- import java.util.Random;
- /**
- * 测试类关于ThreadLocal
- * @author kang
- *
- */
- public class ThreadLocalTest {
- private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();
- public static void main(String[] args) {
- ThreadLocal<String> ss = new ThreadLocal<String>();
- //制造两个线程
- for (int i = 0; i < 2; i++) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- //生成一个随机数并打印
- int data = new Random().nextInt();
- System.out.println(Thread.currentThread().getName()
- + " has put data :" + data);
- //将随机数放入两个不同的ThreadLocal中
- x.set(data);
- MyThreadScopeData.getThreadInstance().setName("name" + data);
- MyThreadScopeData.getThreadInstance().setAge(data);
- //从ThreadLocal中取出数据并打印
- new A().get();
- new B().get();
- System.out.println("#########################################");
- }
- }).start();
- }
- }
- //内部类A,从两个ThreadLocal对象中取出数据,并打印
- static class A {
- public void get() {
- //从value为int类型的ThreadLocal中取出数据,并打印
- int data = x.get();
- System.out.println("A from " + Thread.currentThread().getName()
- + " get int :" + data);
- //从ThreadLocal实体对象中取出线程中放入的数据
- MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
- System.out
- .println("A from " + Thread.currentThread().getName()
- + " 实体对象数据: " + myData.getName() + ","
- + myData.getAge());
- }
- }
- //内部类B,从两个ThreadLocal对象中取出数据,并打印
- static class B {
- public void get() {
- //从value为int类型的ThreadLocal中取出数据,并打印
- int data = x.get();
- System.out.println("B from " + Thread.currentThread().getName()
- + " get int :" + data);
- //从ThreadLocal实体对象中取出线程中放入的数据
- MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
- System.out
- .println("B from " + Thread.currentThread().getName()
- + " 实体对象数据: " + myData.getName() + ","
- + myData.getAge());
- }
- }
- }
我们看一下执行的结果:
Thread-1 has putdata :-458782705
A from Thread-1 getint :-458782705
A from Thread-1实体对象数据: name-458782705,-458782705
B from Thread-1 getint :-458782705
B from Thread-1实体对象数据: name-458782705,-458782705
#########################################
Thread-0 has putdata :1881149941
A from Thread-0 getint :1881149941
A from Thread-0实体对象数据: name1881149941,1881149941
B from Thread-0 getint :1881149941
B from Thread-0实体对象数据: name1881149941,1881149941
#########################################
通过执行结果我们可以看出,在两个线程的执行过程中生成的随机数是不一样的,通过将数据放入ThreadLocal中并取出打印,我们发现每个线程中的数据是一致保持一致的,这也就证明了,ThreadLocal在同一线程中实现了线程内的数据共享,不同线程间我们实现了数据的隔离性。
ThreadLocal源码
我们现在来看下ThreadLocal背后的代码是怎样实现的。
先来看下ThreadLocal的常用方法:
- public T get() { }
- public void set(T value) { }
- public void remove() { }
get()方法是用来获取ThreadLocal在当前线程中保存的变量副本,set()用来设置当前线程中变量的副本,remove()用来移除当前线程中变量的副本
首先我们先来看下ThreadLocal类是如何为每个线程创建变量的副本的:
- /**
- * Returns the value in the current thread's copy of this
- * thread-local variable. If the variable has no value for the
- * current thread, it is first initialized to the value returned
- * by an invocation of the {@link #initialValue} method.
- *
- * @return the current thread's value of this thread-local
- */
- 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();
- }
首先通过currentThread()获取当前运行线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap,如果map不为空就通过当前的ThreadLocal取出map中存取的value,如果为空就调用setInitialValue()方法创建ThreadLocalMap并经value进行返回。
我们继续对上面的get()方法进行详细的分析,我们接下来看下getMap(t)方法,
- /**
- * Get the map associated with a ThreadLocal. Overridden in
- * InheritableThreadLocal.
- *
- * @param t the current thread
- * @return the map
- */
- ThreadLocalMap getMap(Thread t) {
- return t.threadLocals;
- }
意思就是利用currentThread()获取当前运行线程t,然后得到t的成员变量threadLocals,threadLocals又是什么,我们接着往下看,
- /* ThreadLocal values pertaining to this thread. This map is maintained
- * by the ThreadLocal class. */
- ThreadLocal.ThreadLocalMap threadLocals = null;
threadLocals是ThreadLocal类的一个内部类ThreadLocalMap,我们接着看内部类ThreadLocalMap的实现,
- /**
- * ThreadLocalMap is a customized hash map suitable only for
- * maintaining thread local values. No operations are exported
- * outside of the ThreadLocal class. The class is package private to
- * allow declaration of fields in class Thread. To help deal with
- * very large and long-lived usages, the hash table entries use
- * WeakReferences for keys. However, since reference queues are not
- * used, stale entries are guaranteed to be removed only when
- * the table starts running out of space.
- */
- static class ThreadLocalMap {
- /**
- * The entries in this hash map extend WeakReference, using
- * its main ref field as the key (which is always a
- * ThreadLocal object). Note that null keys (i.e. entry.get()
- * == null) mean that the key is no longer referenced, so the
- * entry can be expunged from table. Such entries are referred to
- * as "stale entries" in the code that follows.
- */
- static class Entry extends WeakReference<ThreadLocal<?>> {
- /** The value associated with this ThreadLocal. */
- Object value;
- Entry(ThreadLocal<?> k, Object v) {
- super(k);
- value = v;
- }
- }
可以看到ThreadLocalMap的内部类Entry继承了WeakReference,并且使用ThreadLocal作为键值,利用ThreadLocal的变量作为value,代码跟到这里我的目的就达到了,所以这里ThreadLocalMap本质就是利用Entry构造了一个key、value对,其实关于ThreadLocal的核心代码都在这个类中,感兴趣的同学可以自己接着往下看。
我们get()方法里还有setInitialValue(),是当map为空时返回value用的,我们看下它的代码:
- /**
- * Variant of set() to establish initialValue. Used instead
- * of set() in case user has overridden the set() method.
- *
- * @return the initial value
- */
- 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()获取value,获取当前线程,如果map不为空就对map进行赋值要注意key是当前的ThreadLocal,为空就进行创建map与直接赋值不一样key为当前Thread,同一个map为什么不一样,这里就卖个关子不再介绍了,大家在往下跟层代码看看就明白了。
我们在看setInitialValue()中的方法initialValue()如何来获取value的
- /**
- * An extension of ThreadLocal that obtains its initial value from
- * the specified {@code Supplier}.
- */
- static final class SuppliedThreadLocal<T> extends ThreadLocal<T> {
- private final Supplier<? extends T> supplier;
- SuppliedThreadLocal(Supplier<? extends T> supplier) {
- this.supplier = Objects.requireNonNull(supplier);
- }
- @Override
- protected T initialValue() {
- return supplier.get();
- }
- }
通过ThreadLocal的内部类SuppliedThreadLocal的initialValue()方法得到泛型,并进行返回。
到这里我们就将get()方法如何为ThreadLocal的每个线程创建变量的副本的详细的介绍完了,跟踪完了以后发现确实挺简单,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。