ThreadLocal

Posted chinano1

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal相关的知识,希望对你有一定的参考价值。

1.介绍

      深挖过threadLocal之后,一句话概括:Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。所以ThreadLocal的应用场合,最适合的是按线程多实例(每个线程对应一个实例)的对象的访问,并且这个对象很多地方都要用到。

      早在JDK 1.2的版本中就提供java.lang.ThreadLocal,ThreadLocal为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。

  从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。所以,在Java中编写线程局部变量的代码相对来说要笨拙一些,因此造成线程局部变量没有在Java开发者中得到很好的普及。

ThreadLocal、ThreadLocal、Thread之间的关系

  ThreadLocalMap是ThreadLocal内部类,由ThreadLocal创建,用来存储数据,采用类似hashmap机制,存储了以threadLocal为key,需要隔离的数据为value的Entry键值对数组结构。Thread有ThreadLocal.ThreadLocalMap类型的属性。

        ThreadLocal,有个ThreadLocalMap类型的属性,连接ThreadLocalMap和Thread。来处理Thread的TheadLocalMap属性,包括init初始化属性赋值、get对应的变量,set设置变量等。通过当前线程,获取线程上的ThreadLocalMap属性,对数据进行get、set等操作。

源码如下:

Thread的属性:

 1 public
 2 class Thread implements Runnable {
 3     /*...其他属性...*/
 4  
 5     /* ThreadLocal values pertaining to this thread. This map is maintained
 6      * by the ThreadLocal class. */
 7     ThreadLocal.ThreadLocalMap threadLocals = null;
 8  
 9     /*
10      * InheritableThreadLocal values pertaining to this thread. This map is
11      * maintained by the InheritableThreadLocal class.
12      */
13     ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

ThreadLocal和ThreadLocalMap:

 1 public class ThreadLocal<T> {
 2     /**..其他属性和方法稍后介绍...*/
 3     /**
 4      * ThreadLocalMap is a customized hash map suitable only for
 5      * maintaining thread local values. No operations are exported
 6      * outside of the ThreadLocal class. The class is package private to
 7      * allow declaration of fields in class Thread.  To help deal with
 8      * very large and long-lived usages, the hash table entries use
 9      * WeakReferences for keys. However, since reference queues are not
10      * used, stale entries are guaranteed to be removed only when
11      * the table starts running out of space.
12      */
13     static class ThreadLocalMap {

由ThreadLocal对Thread的TreadLocalMap进行赋值:

 1     /**
 2      * Create the map associated with a ThreadLocal. Overridden in
 3      * InheritableThreadLocal.
 4      *
 5      * @param t the current thread
 6      * @param firstValue value for the initial entry of the map
 7      */
 8     void createMap(Thread t, T firstValue) {
 9         t.threadLocals = new ThreadLocalMap(this, firstValue);
10     }

2.使用

ThreadLocal类核心方法set、get、initialValue、withInitial、setInitialValue、remove:

 

  1    /**
  2      * Returns the current thread‘s "initial value" for this
  3      * thread-local variable.  This method will be invoked the first
  4      * time a thread accesses the variable with the {@link #get}
  5      * method, unless the thread previously invoked the {@link #set}
  6      * method, in which case the {@code initialValue} method will not
  7      * be invoked for the thread.  Normally, this method is invoked at
  8      * most once per thread, but it may be invoked again in case of
  9      * subsequent invocations of {@link #remove} followed by {@link #get}.
 10      *
 11      * <p>This implementation simply returns {@code null}; if the
 12      * programmer desires thread-local variables to have an initial
 13      * value other than {@code null}, {@code ThreadLocal} must be
 14      * subclassed, and this method overridden.  Typically, an
 15      * anonymous inner class will be used.
 16      *
 17      * @return the initial value for this thread-local
 18      */
 19     protected T initialValue() {
 20         return null;
 21     }
 22  
 23     /**
 24      * Creates a thread local variable. The initial value of the variable is
 25      * determined by invoking the {@code get} method on the {@code Supplier}.
 26      *
 27      * @param <S> the type of the thread local‘s value
 28      * @param supplier the supplier to be used to determine the initial value
 29      * @return a new thread local variable
 30      * @throws NullPointerException if the specified supplier is null
 31      * @since 1.8
 32      */
 33     public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
 34         return new SuppliedThreadLocal<>(supplier);
 35     }
 36  
 37     /**
 38      * Creates a thread local variable.
 39      * @see #withInitial(java.util.function.Supplier)
 40      */
 41     public ThreadLocal() {
 42     }
 43  
 44     /**
 45      * Returns the value in the current thread‘s copy of this
 46      * thread-local variable.  If the variable has no value for the
 47      * current thread, it is first initialized to the value returned
 48      * by an invocation of the {@link #initialValue} method.
 49      *
 50      * @return the current thread‘s value of this thread-local
 51      */
 52     public T get() {
 53         Thread t = Thread.currentThread();
 54         ThreadLocalMap map = getMap(t);
 55         if (map != null) {
 56             ThreadLocalMap.Entry e = map.getEntry(this);
 57             if (e != null) {
 58                 @SuppressWarnings("unchecked")
 59                 T result = (T)e.value;
 60                 return result;
 61             }
 62         }
 63         return setInitialValue();
 64     }
 65  
 66     /**
 67      * Variant of set() to establish initialValue. Used instead
 68      * of set() in case user has overridden the set() method.
 69      *
 70      * @return the initial value
 71      */
 72     private T setInitialValue() {
 73         T value = initialValue();
 74         Thread t = Thread.currentThread();
 75         ThreadLocalMap map = getMap(t);
 76         if (map != null)
 77             map.set(this, value);
 78         else
 79             createMap(t, value);
 80         return value;
 81     }
 82  
 83     /**
 84      * Sets the current thread‘s copy of this thread-local variable
 85      * to the specified value.  Most subclasses will have no need to
 86      * override this method, relying solely on the {@link #initialValue}
 87      * method to set the values of thread-locals.
 88      *
 89      * @param value the value to be stored in the current thread‘s copy of
 90      *        this thread-local.
 91      */
 92     public void set(T value) {
 93         Thread t = Thread.currentThread();
 94         ThreadLocalMap map = getMap(t);
 95         if (map != null)
 96             map.set(this, value);
 97         else
 98             createMap(t, value);
 99     }
100  
101     /**
102      * Removes the current thread‘s value for this thread-local
103      * variable.  If this thread-local variable is subsequently
104      * {@linkplain #get read} by the current thread, its value will be
105      * reinitialized by invoking its {@link #initialValue} method,
106      * unless its value is {@linkplain #set set} by the current thread
107      * in the interim.  This may result in multiple invocations of the
108      * {@code initialValue} method in the current thread.
109      *
110      * @since 1.5
111      */
112      public void remove() {
113          ThreadLocalMap m = getMap(Thread.currentThread());
114          if (m != null)
115              m.remove(this);
116      }

 

  • initialValue返回该线程局部变量的初始值。该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
  • withInitial提供一个Supplier的lamda表达式用来当做初始值,java8引入。
  • setInitialValue设置初始值。在get操作没有对应的值时,调用此方法。private方法,防止被覆盖。过程和set类似,只不过是用initialValue作为value进行设置。
  • set设置当前线程对应的线程局部变量的值。先取出当前线程对应的threadLocalMap,如果不存在则用创建一个,否则将value放入以this,即threadLocal为key的映射的map中,其实threadLocalMap内部和hashMap机制一样,存储了Entry键值对数组,后续会深挖threadLocalMap。
  • get该方法返回当前线程所对应的线程局部变量。和set类似,也是先取出当前线程对应的threadLocalMap,如果不存在则用创建一个,但是是用inittialValue作为value放入到map中,且返回initialValue,否则就直接从map取出this即threadLocal对应的value返回。
  • remove将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。需要注意的是,如果remove之后又调用了get,会重新初始化一次,即再次调用initialValue方法,除非在get之前调用set设置过值。

 

3.案例

本案例在多线程中对ThreadLocal和线程局部变量进行测试

1.首先,测试一下线程全局变量在多个线程下的使用,代码如下:

 1 import java.util.Random;
 2 
 3 public class testThreadLocal extends Thread{ 6     
 7     private static boolean flag;
 8     
 9     public void run()
10     {
11         Random random=new Random();
12         int a=random.nextInt(20);
13                 flag=flag(a);
14         System.out.println("其他线程"+a+"启动,flag="+flag);
15         }
16         
17     public static void main(String[] args) {    
18         flag=flag(3);
19         System.out.println("主线程启动,flag="+flag);
20     
21         for (int i = 0; i < 10; i++) {
22             new testThreadLocal().start();
23         }    
24     }
25     
26     public static boolean flag(int a){
27         if(a>4){
28             flag=true;
29             return flag;
30         }else {
31             return flag;
32         }
33     }
34 }
35         

结果如下:

技术分享图片

明显有问题,所以在多线程环境下,是不能使用线程全局变量的

2.线程局部变量测试,代码如下:

 1 import java.util.Random;
 2 
 3 public class testThreadLocal extends Thread{
 4         public void run()
 5     {
 6         Random random=new Random();
 7         int a=random.nextInt(20);
 8         boolean flag=flag(a,false);
 9         System.out.println("其他线程"+a+"启动,flag="+flag);
10     }
11         public static void main(String[] args) {
12         
13         boolean flag=flag(3,false);
14         System.out.println("主线程启动,flag="+flag);
15     
16         for (int i = 0; i < 10; i++) {
17             new testThreadLocal().start();
18         }
19         
20     }
21     
22     public static boolean flag(int a,boolean flag){
23         if(a>4){
24             flag=true;
25             return flag;
26         }else {
27             return flag;
28         }
29     }
30 }

运行结果如下:

技术分享图片

结果正常!

3.ThreadLocal测试

 1 import java.util.Random;
 2 
 3 public class testThreadLocal extends Thread{
 4     
 5     private static ThreadLocal<Boolean> tlflag=new ThreadLocal<>();
 6 
 7     public void run()
 8     {
 9         Random random=new Random();
10         int a=random.nextInt(20);
11         flag(a);
12         System.out.println("ThreadLocal形式,其他线程"+a+"启动,flag="+tlflag.get());
13     }
14     
15     public static void main(String[] args) {
16         
17         flag(3);
18         System.out.println("ThreadLocal形式,主线程启动,flag="+tlflag.get());
19         
20         for (int i = 0; i < 10; i++) {
21             new testThreadLocal().start();
22         }
23     }
24 
25         public static void flag(int a){
26         
27         if(a>4){
28             tlflag.set(true);
29         }else {
30             tlflag.set(false);
31         }
32     }
33 }

运行结果为:

技术分享图片

结果正常!

部分内容参考自博客:https://blog.csdn.net/lufeng20/article/details/24314381

以上是关于ThreadLocal的主要内容,如果未能解决你的问题,请参考以下文章

源代码系列02——ThreadLocal源码分析(基础篇)

java中的ThreadLocal详解及示例代码

ThreadLocal介绍

MyBatis基础:使用java提供的ThreadLocal类优化代码

Java 单线程代码ThreadLocal串值问题

ThreadLocal