• 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

  • 相关阅读:
    PHP与WCF第一次亲密接触
    PHP操作MongoDB
    如何用SVN进行个人版本管理
    【Android】还原“微信”apk中的“发现”和“我”两个模块
    使php支持mbstring库
    mysql 与 mysqli的区别
    nginx 配置正向 HTTP 代理服务器[转]
    正向代理与反向代理的区别【Nginx读书笔记】
    为什么要使用Nginx?
    【转】关于HTTP中文翻译的讨论
  • 原文地址:https://www.cnblogs.com/chinano1/p/9241528.html
Copyright © 2020-2023  润新知