一、ThreadLocal简介
从Java官方文档中的描述:ThreadLocal类用来提供线程内部的局部变量。 这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程的变量。ThreadLocal的实例通常来说都是private static 类型的,用于关联线程和线程上下文。
我们可以得知ThreadLocal的作用是:提供线程内部的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量传递的复杂度。
ThreadLocal功能总结:
- 线程并发:在多线程并发的场景下;
- 传递数据:可以通过ThreadLocal在同一个线程,不同组件中传递公共变量;
- 线程隔离:每个线程的变量都是独立的,不会互相影响;
ThreadLocal的应用场景:
- 前面执行的方法保存了信息后,后续方法可以通过ThreadLocal 直接获取到,避免了传参,类似于全局变量的概念,例如当前登录用户放入ThreadLocal 中,避免每个方法一直传递过去,直接从ThreadLocal获取即可;
- 将数据库Connection连接对象存入ThreadLocal中,保证了不同的线程使用线程相关的Connection,而不会使用其他线程的Connection;
二、ThreadLocal的基本使用
在使用之前,我们先来看看ThreadLocal的一些常用方法:
方法声明 | 作用 |
ThreadLocal() | 创建ThreadLocal对象 |
public void set(T value) | 设置当前线程绑定的局部变量 |
public T get() | 获取当前线程绑定的局部变量 |
public void remove() | 移除当前线程绑定的局部变量 |
我们来看下面这个示例,感受一下ThreadLocal线程隔离的特点:
package com.wsh.mybatis.mybatisdemo.entity; public class ThreadLocalDemo { private static ThreadLocal threadLocal = new ThreadLocal<>(); public static void main(String[] args) { new Thread(() -> { //设置t1线程中本地变量的值 threadLocal.set("t1线程"); //获取t1线程中本地变量的值 System.out.println("t1线程局部变量的value : " + threadLocal.get()); }, "t1").start(); new Thread(() -> { //设置t2线程中本地变量的值 threadLocal.set("t2线程"); //获取t1线程中本地变量的值 System.out.println("t2线程局部变量的value : " + threadLocal.get()); }, "t2").start(); } }
下面是运行后的结果:
t1线程局部变量的value : t1线程 t2线程局部变量的value : t2线程
可以看到,每个线程只会拿到属于它们的线程局部变量副本信息,不会出现t1线程拿到t2线程的变量value,这就是ThreadLocal的线程隔离性。
三、ThreadLocal与Synchronized的区别
在前面的案例中,其实我们也可以使用synchronized关键字加锁来达到同样的线程隔离的效果,虽然ThreadLocal与synchronized关键字都用于处理多线程并发访问变量的问题,不过两者处理问题的角度和思路不同。
区别 | Synchronized | ThreadLocal |
原理 | 同步机制采用“以时间换空间”的方式,只提供了一份变量,让不同的线程排队访问 | ThreadLocal采用“以空间换时间”的方式,为每一个线程都提供了一份变量的副本,从而实现同时访问而相不干扰 |
侧重点 | 多个线程之间访问资源的同步 | 多线程中让每个线程之间的数据相互隔离 |
四、ThreadLocal的内部结构
通过以上的学习,我们对ThreadLocal的作用有了一定的认识。现在我们一起来看一下ThreadLocal的内部结构,有利于我们理解ThreadLocal实现线程数据隔离的原理。
JDK最早期的ThreadLocal的设计:每个ThreadLocal都创建一个ThreadLocalMap,然后用线程thread对象作为Map的key,要存储的局部变量作为Map的value,这样就能达到各个线程的局部变量隔离的效果。大概如下图所示:
在JDK1.8中,JDK后面优化了设计方案:每个线程维护一个ThreadLocalMap,这个Map的key是ThreadLocal实例本身,value才是真正要存储的值Object。
具体的过程如下:
- 每个线程Thread内部都有一个ThreadLocalMap对象;
- ThreadLocalMap里面存储ThreadLocal对象(key)和线程的变量副本(value);
- 线程内部的ThreadLocalMap是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值;
- 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰;
大体结构图如下所示:
比较上面两种方案,JDK1.8优化后的好处主要有下面两点:
(1)、每个ThreadLocalMap中存储的Entry数量变少;(因为1.8之前的设计,thread线程越多,ThreadLocalMap中的Entry数量自然就越多;而1.8之后,Entry的数量是由ThreadLocal的数量来决定的);
(2)、当Thread线程销毁的时候,ThreadLocalMap也会随之销毁,减少内存的使用;
五、ThreadLocal的核心方法源码
基于ThreadLocal的内部结构,我们继续分析ThreadLocal的核心方法源码,更加深入的了解其操作原理。除了构造方法外,ThreadLocal对外暴露的方法主要有下面四个:
方法声明 | 描述 |
protected T initialValue() | 返回当前线程局部变量的初始值 |
public void set(T value) | 设置当前线程绑定的局部变量 |
public T get() | 获取当前线程绑定的局部变量 |
public void remove() | 移除当前线程绑定的局部变量 |
set方法:设置当前线程对应的ThreadLocal的值
public void set(T value) { //获取当前线程对象 Thread t = Thread.currentThread(); //获取当前线程中维护的ThreadLocalMap对象 //getMap(t)实际上就是获取当前线程对象自己的成员变量threadLocals ThreadLocalMap map = getMap(t); //判断ThreadLocalMap是否存在 if (map != null) //如果ThreadLocalMap存在的话,调用map.set设置实体entry //key为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值 map.set(this, value); else //当前线程thread不存在ThreadLocalMap对象,那么调用createMap方法创建一个ThreadLocalMap对象 //t.threadLocals:将当前ThreadLocal对象和value(线程变量的副本)作为第一个entry, 存放至ThreadLocalMap对象中 createMap(t, value); } //获取当前线程thread对应维护中的ThreadLocalMap对象 ThreadLocalMap getMap(Thread t) { return t.threadLocals; } //初始化一个ThreadLocalMap对象 void createMap(Thread t, T firstValue) { //java.lang.Thread#threadLocals //ThreadLocal.ThreadLocalMap threadLocals = null; //并设置到当前线程thread的属性threadLocals中 t.threadLocals = new ThreadLocalMap(this, firstValue); }
get()方法:获取当前线程的局部变量的副本
public T get() { //获取当前线程对象 Thread t = Thread.currentThread(); //获取当前线程维护的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); //如果ThreadLocalMap对象存在 if (map != null) { //以当前threadLocal为key,调用getEntry方法获取对应的存储实体entry ThreadLocalMap.Entry e = map.getEntry(this); //判断entry对象是否为空 if (e != null) { @SuppressWarnings("unchecked") //获取存储实体entry对应的value值 //即为我们想要的当前线程对应此ThreadLocal的值 T result = (T)e.value; return result; } } //初始化:有两种情况会执行下面代码: //1. map不存在,表示当前线程没有维护的ThreadLocalMap对象; //2. map存在,但是没有与当前ThreadLocal管理的Entry对象; return setInitialValue(); } //初始化 private T setInitialValue() { //调用initialValue()获取初始化值 //可以被子类重写,如果没有重写默认返回null T value = initialValue(); //获取当前线程 Thread t = Thread.currentThread(); //获取此线程对象中维护的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); //如果ThreadLocalMap不为空 if (map != null) //设置value初始值为initialValue()方法返回的值 map.set(this, value); else //如果ThreadLocalMap为空,创建一个ThreadLocalMap对象 //t.threadLocals:将当前ThreadLocal对象和value(线程变量的副本)作为第一个entry存放至ThreadLocalMap对象中 createMap(t, value); return value; }
remove():移除当前线程局部变量的副本
public void remove() { //获取到当前线程的ThreadLocalMap对象 ThreadLocalMap m = getMap(Thread.currentThread()); //如果ThreadLocalMap对象不为空 if (m != null) //以当前ThreadLocal对象作为key从ThreadLocalMap对象中移除对应的实体Entry对象 m.remove(this); }
initialValue():设置当前线程局部变量的初始值
//调用initialValue()获取初始化值 //可以被子类重写,如果没有重写默认返回null protected T initialValue() { return null; }
全面理解ThreadLocal(详细简单)(二)https://developer.aliyun.com/article/1393275