Java中的ThreadLocal详解

一、ThreadLocal简介多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线...

Java中的ThreadLocal详解

一、ThreadLocal简介

  多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。

  ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一乐ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题,如下图所示

二、ThreadLocal简单使用

  下面的例子中,开启两个线程,在每个线程内部设置了本地变量的值,然后调用print方法打印当前本地变量的值。如果在打印之后调用本地变量的remove方法会删除本地内存中的变量,代码如下所示

 1 package test; 2  3 public class ThreadLocalTest { 4  5  static ThreadLocal<String> localVar = new ThreadLocal<>(); 6  7  static void print(String str) { 8//打印当前线程中本地内存中本地变量的值 9System.out.println(str" :"   localVar.get());10//清除本地内存中的本地变量11   localVar.remove();12  }13 14  public static void main(String[] args) {15Thread t1  = new Thread(new Runnable() {16 @Override17 public void run() {18  //设置线程1中本地变量的值19  localVar.set("localVar1");20  //调用打印方法21  print("thread1");22  //打印本地变量23  System.out.println("after remove : "   localVar.get());24 }25   });26 27Thread t2  = new Thread(new Runnable() {28 @Override29 public void run() {30  //设置线程1中本地变量的值31  localVar.set("localVar2");32  //调用打印方法33  print("thread2");34  //打印本地变量35  System.out.println("after remove : "   localVar.get());36 }37   });38 39   t1.start();40   t2.start();41  }42 }

下面是运行后的结果:

三、ThreadLocal的实现原理

  下面是ThreadLocal的类图结构,从图中可知:Thread类中有两个变量threadLocals和inheritableThreadLocals,二者都是ThreadLocal内部类ThreadLocalMap类型的变量,我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap。在默认情况下,每个线程中的这两个变量都为null,只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们(后面我们会查看这两个方法的源码)。除此之外,和我所想的不同的是,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面(前面也说过,该变量是Thread类的变量)。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量。下面我们通过查看ThreadLocal的set、get以及remove方法来查看ThreadLocal具体实怎样工作的

  1、set方法源码

 1 public void set(T value) { 2  //(1)获取当前线程(调用者线程) 3  Thread t = Thread.currentThread(); 4  //(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map 5  ThreadLocalMap map = getMap(t); 6  //(3)如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值 7  if (map != null) 8map.set(this, value); 9  //(4)如果map为null,说明首次添加,需要首先创建出对应的map10  else11   createMap(t, value);12 }

  在上面的代码中,(2)处调用getMap方法获得当前线程对应的threadLocals(参照上面的图示和文字说明),该方法代码如下

ThreadLocalMap getMap(Thread t) { return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上}

  如果调用getMap方法返回值不为null,就直接将value值设置到threadLocals中(key为当前线程引用,值为本地变量);如果getMap方法返回null说明是第一次调用set方法(前面说到过,threadLocals默认值为null,只有调用set方法的时候才会创建map),这个时候就需要调用createMap方法创建threadLocals,该方法如下所示

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

  createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。

  2、get方法源码

  在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null。

 1 public T get() { 2  //(1)获取当前线程 3  Thread t = Thread.currentThread(); 4  //(2)获取当前线程的threadLocals变量 5  ThreadLocalMap map = getMap(t); 6  //(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值 7  if (map != null) { 8ThreadLocalMap.Entry e = map.getEntry(this); 9if (e != null) {10 @SuppressWarnings("unchecked")11 T result = (T)e.value;12 return result;13   }14  }15  //(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量16  return setInitialValue();17 }18 19 private T setInitialValue() {20  //protected T initialValue() {return null;}21  T value = initialValue();22  //获取当前线程23  Thre
源文地址:https://www.guoxiongfei.cn/cntech/19360.html