Java 类 ThreadLocal 本地线程变量

前言:工作中将要使用ThreadLocal,先学习总结一波。有不对的地方欢迎评论指出。

定义

  ThreadLocal并不是一个Thread,而是Thread的局部变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其get或set方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。

作用

  实现每一个线程都有自己的共享变量。

使用方法

  
图片描述

  initialValue:返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的,默认就是null。

  remove方法:将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 1.5 新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。

源码解析

  ThreadLocal 构造方法:

  
图片描述

  initialValue() 方法:

  
图片描述

  get() 方法:

  
图片描述

  第一行为获取线程的当前活动线程

  
图片描述

  然后获取到当前线程的ThreadLocalMap 对象,再通过当前threadLocal来获取这个map对象的键值对,从而取出当前threadLocal中存的变量副本。

  如果ThreadLocalMap 对象为空,或这个map里面还没有存当前threadLocal的变量副本,则调用setInitialValue(); 

  set() 方法:

  
图片描述

  如果当前线程里面有线程变量map,则给当前线程变量(this)设置值(value);如果没有,则创建当前线程的线程变量map,并设置值。

  getMap() 方法:

  
图片描述

  
图片描述

  getMap() 方法中,返回了当前线程的变量,threadLocals,类型为ThreadLocalMap。

  setInitialValue() 方法:

  
图片描述

  setInitialValue() 方法,主要是设置初始化的 当前线程变量的变量副本。如果当前线程里面还没有 当前线程变量Map(ThreadLocalMap),则,初始化当前线程(thread)的线程变量Map

  createMap() 方法:

  
图片描述

  初始化当前线程(thread)的线程变量Map

  ThreadLocalMap 内部类:

  
图片描述

  构造方法:

  
图片描述

  从这里可以看出,threadLocalMap里面存的key值就是 ThreadLocal 对象。

  remove() 方法:

  
图片描述

举例 验证线程变量的隔离性

 /**
  * 本地线程变量 test
  * Created by yule on 2018/6/26 22:35.
  */
 public class ThreadLocalTest {
     public static void main(String[] args) throws InterruptedException {
         ThreadDemo1 threadDemo1 = new ThreadDemo1();
         threadDemo1.start();
 
         Thread.sleep(100);
 
         ThreadDemo2 threadDemo2 = new ThreadDemo2();
         threadDemo2.start();
 
         ThreadLocalTools.stringThreadLocal.set("main设置值");
         System.out.println(ThreadLocalTools.stringThreadLocal.get());
     }
 }
 
 class ThreadLocalTools{
     public static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
 }
 
 class ThreadDemo1 extends Thread{
     @Override
     public void run() {
         super.run();
         for(int i = 0; i < 10; i++){
             System.out.println(ThreadLocalTools.stringThreadLocal.get());
             ThreadLocalTools.stringThreadLocal.set("ThreadDemo1设置值");
             try {
                 Thread.sleep(100);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     }
 }
 
 class ThreadDemo2 extends Thread{
     @Override
     public void run() {
         super.run();
         for(int i = 0; i < 10; i++){
             System.out.println(ThreadLocalTools.stringThreadLocal.get());
             ThreadLocalTools.stringThreadLocal.set("ThreadDemo2设置值");
             try {
                 Thread.sleep(100);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }
     }
 }

图片描述

  输出的第一个为null是因为在set()方法前调用get()方法,会给出initialValue()方法的值,默认为null。

总结

  ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。

  ThreadLocal通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。

  ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单:在ThreadLocal类中定义了一个ThreadLocalMap,每一个Thread中都有一个该类型的变量——threadLocals——用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本。

  通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值。