全面理解ThreadLocal(详细简单)(一)

简介: 全面理解ThreadLocal(详细简单)

一、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,这样就能达到各个线程的局部变量隔离的效果。大概如下图所示:

image.png

在JDK1.8中,JDK后面优化了设计方案:每个线程维护一个ThreadLocalMap,这个Map的key是ThreadLocal实例本身,value才是真正要存储的值Object。

具体的过程如下:

  • 每个线程Thread内部都有一个ThreadLocalMap对象;
  • ThreadLocalMap里面存储ThreadLocal对象(key)和线程的变量副本(value);
  • 线程内部的ThreadLocalMap是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值;
  • 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰;

大体结构图如下所示:

image.png

比较上面两种方案,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

相关文章
|
6天前
|
存储 Java Spring
ThreadLocal用法
ThreadLocal用法
17 0
|
6天前
|
存储 安全 Java
面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?
字节面试题:用过ThreadLocal吗?ThreadLocal是在哪个包下的?看过ThreadLocal源码吗?讲一下ThreadLocal的get和put是怎么实现的?
37 0
|
6天前
|
存储 安全 Java
ThreadLocal原理讲解
ThreadLocal原理讲解
28 0
|
9月前
|
Java
ThreadLocal全面解析
ThreadLocal全面解析
75 0
|
6天前
|
存储 算法 Java
全面理解ThreadLocal(详细简单)(二)
全面理解ThreadLocal(详细简单)
28 0
全面理解ThreadLocal(详细简单)(二)
|
10月前
|
存储 SpringCloudAlibaba Java
浅析ThreadLocal使用及实现原理
提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其`get` 或 `set`方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。`ThreadLocal`实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联 。所以ThreadLocal与线程同步机制不同,线程同步机制是多个线程共享同一个变量,而ThreadLocal是为每一个线程创建一个单独的变量副本,故而每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本。可以这么说Th
80 0
浅析ThreadLocal使用及实现原理
|
机器学习/深度学习 存储 算法
ThreadLocal源码分析-黄金分割数的使用(上)
最近接触到的一个项目要兼容新老系统,最终采用了ThreadLocal(实际上用的是InheritableThreadLocal)用于在子线程获取父线程中共享的变量。问题是解决了,但是后来发现对ThreadLocal的理解不够深入,于是顺便把它的源码阅读理解了一遍。在谈到ThreadLocal之前先卖个关子,先谈谈黄金分割数。本文在阅读ThreadLocal源码的时候是使用JDK8(1.8.0_181)。
188 0
ThreadLocal源码分析-黄金分割数的使用(上)
|
存储 算法 安全
ThreadLocal原理剖析
ThreadLocal原理剖析
166 0
|
Java 定位技术
ThreadLocal原理
经典八股文之ThreadLocal原理
154 0
|
安全 Java 数据库连接
ThreadLocal类教程
ThreadLocal类教程
110 0