ThreadLocal的简单使用和原理

ThreadLocal介绍

官方介绍

ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能够保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是 private static类型的,用于关联线程和线程上下文。

白话:ThreadLocal的作用是提供线程内的局部变量,不同线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一线程内多个函数或组件之间一些公共变量传递的复杂度。

总结:

  • 线程并发:在多线程并发场景下使用
  • 传递数据:我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
  • 线程隔离:每个线程的变量是独立的,不会相互影响(核心)

基本使用

常用方法:

在使用之前,我们先来认识几个ThreadLocal的常用方法

方法介绍
ThreadLocal()创建ThreadLocal对象(构造方法)
public void set(T value)设置当前线程绑定的局部变量
public T get()获取当前线程绑定的局部变量
public void remove()移除当前线程绑定的局部变量

简单案例:

public class ThreadLocalDemo1 {

    private String demoVariable;    // 变量

    public String getDemoVariable() {
        return demoVariable;
    }

    public void setDemoVariable(String demoVariable) {
        this.demoVariable = demoVariable;
    }

    public static void main(String[] args) {
        ThreadLocalDemo1 demo = new ThreadLocalDemo1();

        for (int i=0; i<5; i++) {
            Thread thread = new Thread(() -> {
                demo.setDemoVariable(Thread.currentThread().getName()+"的数据");
                System.out.println("----------------------------------");
                System.out.println(Thread.currentThread().getName()+"------>"+demo.getDemoVariable());
            });
            thread.setName("线程"+i);
            thread.start();
        }
    }

}

这段代码比较简单,首先一个类中声明一个变量(demoVariable),然后主方法中 创建五个线程同时操作同一个对象(demo)的setDemoVariable和getDemoVariable方法

代码输出结果:

----------------------------------
线程0------>线程0的数据
----------------------------------
----------------------------------
线程2------>线程2的数据
线程1------>线程2的数据
----------------------------------
----------------------------------
线程3------>线程3的数据
线程4------>线程3的数据

Process finished with exit code 0

看输出结果我们会发现数据比较混乱,有的线程获取的是其他线程的变量,例如:线程1------>线程2的数据
其实了解多线程的都知道,这段代码输出的结果是不确定的。多个线程操作同一对象的可变属性是线程不安全的。

那么怎样解决这个问题呢?
其实解决方案有多种,可以使用Jdk自带的synchronized锁、也可以使用基于AQS的锁、也可以利用CAS机制不加锁…这里这些都不是本篇文章的重点,接下来我们使用咱们的主角ThreadLocal来解决这个问题

上代码:

public class ThreadLocalDemo1 {
    private static ThreadLocal<String> t1 = new ThreadLocal<>();

    public String getDemoVariable() {
        return t1.get();    // 从t1(ThreadLocal对象)中获取值
    }

    public void setDemoVariable(String demoVariable) {
        t1.set(demoVariable);   // 将传过来的参数绑定到t1(ThreadLocal对象)中
    }

    public static void main(String[] args) {
        ThreadLocalDemo1 demo = new ThreadLocalDemo1();

        for (int i=0; i<5; i++) {
            Thread thread = new Thread(() -> {
                demo.setDemoVariable(Thread.currentThread().getName()+"的数据");
                System.out.println("----------------------------------");
                System.out.println(Thread.currentThread().getName()+"------>"+demo.getDemoVariable());
            });
            thread.setName("线程"+i);
            thread.start();
        }
    }
}

代码变化:

  1. 在类中增加了一个ThreadLocal对象——t1
  2. 删掉之前的demoVariable变量,因为我们不再操作它了
  3. 修改setDemoVariable方法:将传过来的参数绑定到t1(ThreadLocal对象)中
  4. 修改getDemoVariable方法:从t1(ThreadLocal对象)中获取值

代码输出结果:

----------------------------------
----------------------------------
线程1------>线程1的数据
线程0------>线程0的数据
----------------------------------
线程2------>线程2的数据
----------------------------------
线程3------>线程3的数据
----------------------------------
线程4------>线程4的数据

Process finished with exit code 0

此时我们会发现,每个线程输出的数据已经一一对应上了


ThreadLocal类与synchronized关键字区别

加锁解决方案

加锁解决方案的代码:

public class ThreadLocalDemo1 {

    private String demoVariable;    // 变量

    public String getDemoVariable() {
        return demoVariable;
    }

    public void setDemoVariable(String demoVariable) {
        this.demoVariable = demoVariable;
    }

    public static void main(String[] args) {
        ThreadLocalDemo1 demo = new ThreadLocalDemo1();

        for (int i=0; i<5; i++) {
            Thread thread = new Thread(() -> {
                synchronized (demo) {
                    demo.setDemoVariable(Thread.currentThread().getName()+"的数据");
                    System.out.println("----------------------------------");
                    System.out.println(Thread.currentThread().getName()+"------>"+demo.getDemoVariable());
                }
            });
            thread.setName("线程"+i);
            thread.start();
        }
    }
}

主要代码:

synchronized (demo) {
    demo.setDemoVariable(Thread.currentThread().getName()+"的数据");
    System.out.println("----------------------------------");
    System.out.println(Thread.currentThread().getName()+"------>"+demo.getDemoVariable());
}

其实主要代码就是这一块儿,每个线程在操作demoVariable变量的时候,都为demo这个对象加上锁

代码输出结果:

----------------------------------
线程0------>线程0的数据
----------------------------------
线程1------>线程1的数据
----------------------------------
线程2------>线程2的数据
----------------------------------
线程3------>线程3的数据
----------------------------------
线程4------>线程4的数据
Process finished with exit code 0

从结果可以发现,加锁确实可以解决这个问题,但是在这里我们强调的是线程数据隔离的问题,并不是线程共享数据的问题,在这个案例中使用synchronized关键字是不合适的

ThreadLocal与synchronized的区别:

虽然ThreadLocal模式与synchronized关键字都用于处理多线程并发访问变量的问题,不过两者处理问题的角度和思路不同

  • synchronized:
    • 原理:同步机制采用‘以时间换空间’的方式,只提供一份变量,让不同的线程排队访问
    • 侧重点:多线程之间访问资源的同步
  • ThreadLocal:
    • 原理:ThreadLocal采用‘以空间换时间’的方式,为每个线程都提供一份变量的副本,从而实现同时访问而不互相干扰
    • 侧重点:多线程中让每个线程之间的数据相互隔离

ThreadLocal的内部结构

JDK8中ThreadLocal的设计是:每个Thread维护一个ThreadLocalMap,这个Map的key是ThreadLocal示例本身,value才是真正要存储的值Object。

具体的过程是这样的:

  1. 每个Thread线程内部都有一个Map(ThreadLocalMap)
  2. Map里面存储的是key,value形式的数据,key:ThreadLocal对象,value:线程的变量副本
  3. Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值
  4. 对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离

在这里插入图片描述


ThreadLocal的核心方法源码

基于ThreadLocal的内部结构,我们继续分析它的核心方法源码,更深入地了解其操作原理。
除了构造方法之外,ThreadLocal对外暴露的方法有以下4个:

方法介绍
protected T initialValue()返回当前线程局部变量的初始值(return null)
public void set(T value)设置当前线程绑定的局部变量
public T get()获取当前线程绑定的局部变量
public void remove()移除当前线程绑定的局部变量

set方法

源码和对应的中文注释:

  /**
     * 设置当前线程对应的ThreadLocal的值
     *
     * @param value 将要保存在当前线程对应的ThreadLocal的值
     */
    public void set(T value) {
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 判断map是否存在
        if (map != null)
            // 存在则调用map.set设置此实体entry
            map.set(this, value);
        else
            // 1)当前线程Thread 不存在ThreadLocalMap对象
            // 2)则调用createMap进行ThreadLocalMap对象的初始化
            // 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
            createMap(t, value);
    }

 /**
     * 获取当前线程Thread对应维护的ThreadLocalMap 
     * 
     * @param  t the current thread 当前线程
     * @return the map 对应维护的ThreadLocalMap 
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
	/**
     *创建当前线程Thread对应维护的ThreadLocalMap 
     *
     * @param t 当前线程
     * @param firstValue 存放到map中第一个entry的值
     */
	void createMap(Thread t, T firstValue) {
        //这里的this是调用此方法的threadLocal
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

代码执行流程:

​ A. 首先获取当前线程,并根据当前线程获取一个Map

​ B. 如果获取的Map不为空,则将参数设置到Map中(当前ThreadLocal的引用作为key)

​ C. 如果Map为空,则给该线程创建 Map,并设置初始值

get方法

源码和对应的中文注释:

 /**
     * 返回当前线程中保存ThreadLocal的值
     * 如果当前线程没有此ThreadLocal变量,
     * 则它会通过调用{@link #initialValue} 方法进行初始化值
     *
     * @return 返回当前线程对应此ThreadLocal的值
     */
    public T get() {
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 如果此map存在
        if (map != null) {
            // 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
            ThreadLocalMap.Entry e = map.getEntry(this);
            // 对e进行判空 
            if (e != null) {
                @SuppressWarnings("unchecked")
                // 获取存储实体 e 对应的 value值
                // 即为我们想要的当前线程对应此ThreadLocal的值
                T result = (T)e.value;
                return result;
            }
        }
        /*
        	初始化 : 有两种情况有执行当前代码
        	第一种情况: map不存在,表示此线程没有维护的ThreadLocalMap对象
        	第二种情况: map存在, 但是没有与当前ThreadLocal关联的entry
         */
        return setInitialValue();
    }

    /**
     * 初始化
     *
     * @return the initial value 初始化后的值
     */
    private T setInitialValue() {
        // 调用initialValue获取初始化的值
        // 此方法可以被子类重写, 如果不重写默认返回null
        T value = initialValue();
        // 获取当前线程对象
        Thread t = Thread.currentThread();
        // 获取此线程对象中维护的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        // 判断map是否存在
        if (map != null)
            // 存在则调用map.set设置此实体entry
            map.set(this, value);
        else
            // 1)当前线程Thread 不存在ThreadLocalMap对象
            // 2)则调用createMap进行ThreadLocalMap对象的初始化
            // 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
            createMap(t, value);
        // 返回设置的值value
        return value;
    }

代码执行流程:

​ A. 首先获取当前线程, 根据当前线程获取一个Map

​ B. 如果获取的Map不为空,则在Map中以ThreadLocal的引用作为key来在Map中获取对应的Entry e,否则转到D

​ C. 如果e不为null,则返回e.value,否则转到D

​ D. Map为空或者e为空,则通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的Map

remove方法

源码和对应的中文注释:

 /**
 * 删除当前线程中保存的ThreadLocal对应的实体entry
 */
 public void remove() {
    // 获取当前线程对象中维护的ThreadLocalMap对象
     ThreadLocalMap m = getMap(Thread.currentThread());
    // 如果此map存在
     if (m != null)
        // 存在则调用map.remove
        // 以当前ThreadLocal为key删除对应的实体entry
         m.remove(this);
 }

代码执行流程:
​ A. 首先获取当前线程,并根据当前线程获取一个Map

​ B. 如果获取的Map不为空,则移除当前ThreadLocal对象对应的entry

initialValue方法

源码和对应的中文注释:

/**
 * 返回当前线程对应的ThreadLocal的初始值
  
 * 此方法的第一次调用发生在,当线程通过get方法访问此线程的ThreadLocal值时
 * 除非线程先调用了set方法,在这种情况下,initialValue 才不会被这个线程调用。
 * 通常情况下,每个线程最多调用一次这个方法。
 *  * <p>这个方法仅仅简单的返回null {@code null};
 * 如果程序员想ThreadLocal线程局部变量有一个除null以外的初始值,
 * 必须通过子类继承{@code ThreadLocal} 的方式去重写此方法
 * 通常, 可以通过匿名内部类的方式实现
 *  * @return 当前ThreadLocal的初始值
  */
protected T initialValue() {
    return null;
}

此方法的作用是 返回该线程局部变量的初始值。

  • 这个方法是一个延迟调用方法,从上面的代码我们得知,在set方法还未调用而先调用了get方法时才执行,并且仅执行1次。
  • 这个方法缺省实现直接返回一个null。
  • 如果想要一个除null之外的初始值,可以重写此方法。(备注: 该方法是一个protected的方法,显然是为了让子类覆盖而设计的)

ThreadLocalMap源码分析

未完待续。。。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
龙果 java并发编程原理实战 第2节理解多线程与并发的之间的联系与区别 [免费观看] 00:11:59分钟 | 第3节解析多线程与多进程的联系以及上下文切换所导致资源浪费问题 [免费观看] 00:13:03分钟 | 第4节学习并发的四个阶段并推荐学习并发的资料 [免费观看] 00:09:13分钟 | 第5节线程的状态以及各状态之间的转换详解00:21:56分钟 | 第6节线程的初始化,中断以及其源码讲解00:21:26分钟 | 第7节多种创建线程的方式案例演示(一)带返回值的方式00:17:12分钟 | 第8节多种创建线程的方式案例演示(二)使用线程池00:15:40分钟 | 第9节Spring对并发的支持:Spring的异步任务00:11:10分钟 | 第10节使用jdk8提供的lambda进行并行计算00:14:22分钟 | 第11节了解多线程所带来的安全风险00:13:16分钟 | 第12节从线程的优先级看饥饿问题00:18:42分钟 | 第13节从Java字节码的角度看线程安全性问题00:25:43分钟 | 第14节synchronized保证线程安全的原理(理论层面)00:13:59分钟 | 第15节synchronized保证线程安全的原理(jvm层面)00:25:03分钟 | 第16节单例问题与线程安全性深入解析00:27:15分钟 | 第17节理解自旋锁,死锁与重入锁00:24:58分钟 | 第18节深入理解volatile原理使用00:28:30分钟 | 第19节JDK5提供的原子类的操作以及实现原理00:27:10分钟 | 第20节Lock接口认识与使用00:19:54分钟 | 第21节手动实现一个可重入锁00:26:31分钟 | 第22节AbstractQueuedSynchronizer(AQS)详解00:49:04分钟 | 第23节使用AQS重写自己的锁00:31:04分钟 | 第24节重入锁原理与演示00:12:24分钟 | 第25节读写锁认识与原理00:18:04分钟 | 第26节细读ReentrantReadWriteLock源码00:30:38分钟 | 第27节ReentrantReadWriteLock锁降级详解00:13:32分钟 | 第28节线程安全性问题简单总结00:15:34分钟 | 第29节线程之间的通信之wait/notify00:32:12分钟 | 第30节通过生产者消费者模型理解等待唤醒机制00:20:50分钟 | 第31节Condition的使用原理解析00:17:40分钟 | 第32节使用Condition重写wait/notify案例并实现一个有界队列00:22:05分钟 | 第33节深入解析Condition源码00:21:15分钟 | 第34节实战:简易数据连接池00:24:53分钟 | 第35节线程之间通信之join应用与实现原理剖析00:10:17分钟 | 第36节ThreadLocal 使用及实现原理00:17:41分钟 | 第37节并发工具类CountDownLatch详解00:22:04分钟 | 第38节并发工具类CyclicBarrier 详解00:11:52分钟 | 第39节并发工具类Semaphore详解00:17:27分钟 | 第40节并发工具类Exchanger详解00:13:47分钟 | 第41节CountDownLatch,CyclicBarrier,Semaphore源码解析00:29:57分钟 | 第42节提前完成任务之FutureTask使用00:11:43分钟 | 第43节Future设计模式实现(实现类似于JDK提供的Future)00:19:20分钟 | 第44节Future源码解读00:29:22分钟 | 第45节Fork/Join框架详解00:28:09分钟 | 第46节同步容器与并发容器00:18:44分钟 | 第47节并发容器CopyOnWriteArrayList原理使用00:15:52分钟 | 第48节并发容器ConcurrentLinkedQueue原理使用00:31:03分钟 | 第49节Java中的阻塞队列原理使用00:26:18分钟 | 第50节实战:简单实现消息队列00:11:07分钟 | 第51节并发容器ConcurrentHashMap原理使用00:38:22分钟 | 第52节线程池的原理使用00:42:49分钟 | 第53节Executor框架详解00:36:54分钟 | 第54节实战:简易web服务器(一)00:55:34分钟 | 第55节实战:简易web服务器(二)00:24:36分钟 | 第56节JDK8的新增原子操作类LongAddr原理使用00:17:45分钟 | 第57节JDK8新增锁StampedLock详解00:29:37分钟 | 第58节重排序问题00:23:19分钟 | 第59节happens-before简单概述00:15:17分钟 | 第60节锁的内存语义00:13:54分钟 | 第61节volatile内存语义00:12:04分钟 | 第62节final域的内存语义00:34:07分钟 | 第63节实战:问题定位00:07:48分钟

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coderzpw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值