一.ThreadLocal是什么?

早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
  ThreadLocal,顾名思义,它不是一个线程,而是线程的一个本地化对象。当工作于多线程中的对象使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程分配一个独立的变量副本。所以每一个线程都可以独立地改变自己的副本,而不会影响其他线程所对应的副本。从线程的角度看,这个变量就像是线程的本地变量,这也是类名中“Local”所要表达的意思。


二.举个例子如下:



package cn.liuhaihua;     public class SequenceNumber {           //①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值          private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){              public Integer initialValue(){                  return 0;              }          };           //②获取下一个序列值          public int getNextNum(){              seqNum.set(seqNum.get()+1);              return seqNum.get();          }           public static void main(String[ ] args)           {               SequenceNumber sn = new SequenceNumber();                //③ 3个线程共享sn,各自产生序列号               TestClient t1 = new TestClient(sn);                 TestClient t2 = new TestClient(sn);               TestClient t3 = new TestClient(sn);               t1.start();               t2.start();               t3.start();          }
       private static class TestClient extends Thread          {              private SequenceNumber sn;              public TestClient(SequenceNumber sn) {                  this.sn = sn;              }              public void run()              {                  //④每个线程打出3个序列值                  for (int i = 0; i < 3; i++) {                      System.out.println("thread["+Thread.currentThread().getName()+                              "] sn["+sn.getNextNum()+"]");                  }            }          }      }

运行结果

再有人问你ThreadLocal,就把这篇文章扔给他_java

从现象看一下,为什么每个线程打印出来的数据都不受影响呢?下面我们一起来看下ThreadLocal源码,分析一下原因


三.ThreadLocal源码分析

再有人问你ThreadLocal,就把这篇文章扔给他_java_02

以上源码是jdk1.8版本里面,带着上面的疑问,我们来看一下主要方法,

当我们的线程调用sn.getNextNum()的时候,会执行


        public int getNextNum(){              seqNum.set(seqNum.get()+1);              return seqNum.get();          }

然后会先调用ThreadLocal里面的get方法,方法大概内容,先去ThreadLocalMap看一遍是否存在?

  1. 存在的话,则取出值来,转化成T类型的返回

  2. 不存在的话,然后在调用setInitialValue,返回一个null


  /**     * 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();    }


 protected T initialValue() {        return null;  }  /**     * 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;    }

setInitialValue先查询是否存在值,存在的话,直接覆盖,不存在的话,先创建map在设置 初始化值是一个null


   * Create the map associated with a ThreadLocal. Overridden in     * InheritableThreadLocal.     *     * @param t the current thread     * @param firstValue value for the initial entry of the map     */    void createMap(Thread t, T firstValue) {        t.threadLocals = new ThreadLocalMap(this, firstValue);    }

下面我们再来看一下set(),存储数据的过程, 如我们例子所示,当我们调用

seqNum.set(seqNum.get()+1);  会调用ThreadLocal.set(T value) 方法


 /**     * Sets the current thread's copy of this thread-local variable     * to the specified value.  Most subclasses will have no need to     * override this method, relying solely on the {@link #initialValue}     * method to set the values of thread-locals.     *     * @param value the value to be stored in the current thread's copy of     *        this thread-local.     */    public void set(T value) {        Thread t = Thread.currentThread();        ThreadLocalMap map = getMap(t);        if (map != null)            map.set(this, value);        else            createMap(t, value);    }

如果map存在值的话,会直接覆盖,不存在的话会创建一个map并且初始化值. 上述示例说明,每个线程都是单独维护一套自己的变量,从源码看得出来,其主要作用的是里面的内部类ThreadLocalMap,每个线程单独把自己的变量存在在不同的Entry,从而实现线程之间数据不相互依赖



三.最后总结一下

ThreadLocal适用于资源共享但不需要维护状态的情况,也就是一个线程对资源的修改,不影响另一个线程的运行;这种设计是‘空间换时间’,synchronized顺序执行是‘时间换取空间’。