首页 > 其他分享 >ThreadLocal的深度解读

ThreadLocal的深度解读

时间:2023-12-04 21:27:04浏览次数:44  
标签:map Thread ThreadLocalMap value 解读 ThreadLocal 线程 深度

原文链接:https://zhuanlan.zhihu.com/p/624851777

一、J2SE的原始描述

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

该类提供线程局部变量。这些变量与普通变量的不同之处在于,每个线程访问的变量(通过其get或set方法)都是自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的private static字段,希望将有状态变化的对象与线程关联(例如用户ID或事务ID)。

Each thread holds an implicit reference to its copy of a thread-local variable as long as the thread is alive and the ThreadLocal instance is accessible; after a thread goes away, all of its copies of thread-local instances are subject to garbage collection (unless other references to these copies exist).

只要线程是活的,并且ThreadLocal实例是可访问的,每个线程都持有对其线程局部变量副本的隐式引用,即弱引用(除非存在对这些副本的其他引用)。

二、ThreadLocal解读

ThreadLocal线程变量,即线程局部变量,该ThreadLocal的变量只属于当前线程独享,对其他线程而言是隔离的。ThreadLocal在每个线程中都创建一个副本,每个线程只访问自己的副本。

ThreadLocal 变量通常被private static修饰,因此是同一个对象,并且是同一个 ThreadLocal 对象在不同的 Thread 中创建不同的副本。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收。

三、ThreadLocal示例

package com.observer;
 
public class ThreadLocalTest {
 
	private static ThreadLocal<String> localVar = new ThreadLocal<String>();
 
	public static void print(String str) {
		// 打印当前线程中本地内存中本地变量的值
		System.out.println(str + " :" + localVar.get());
		// 清除本地内存中的本地变量
		localVar.remove();
	}
 
	public static void main(String[] args) throws InterruptedException {
 
		new Thread(new Runnable() {
			public void run() {
				for (int i = 1; i <= 5; i++) {
					ThreadLocalTest.localVar.set("local_A--" + i);
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					print("A-" + i);
					// 清楚后打印
					System.out.println("A-" + i + "---after remove : " + localVar.get());
				}
			}
		}, "A").start();
 
		new Thread(new Runnable() {
			public void run() {
				for (int i = 1; i <= 5; i++) {
					ThreadLocalTest.localVar.set("local_B--" + i);
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					print("B-" + i);
					// 清楚后打印
					System.out.println("B-" + i + "---after remove : " + localVar.get());
				}
			}
		}, "B").start();
	}
}

打印输出:
A-1 :local_A--1
A-1---after remove : null
B-1 :local_B--1
B-1---after remove : null
A-2 :local_A--2
A-2---after remove : null
B-2 :local_B--2
B-2---after remove : null
A-3 :local_A--3
A-3---after remove : null
B-3 :local_B--3
B-3---after remove : null
A-4 :local_A--4
A-4---after remove : null
B-4 :local_B--4
B-4---after remove : null
A-5 :local_A--5
A-5---after remove : null
B-5 :local_B--5
B-5---after remove : null

四、ThreadLocal的原理

4.1、set方法

public void set(T value) {
        //1、获取当前线程
        Thread t = Thread.currentThread();
        //2、获取线程中的属性 threadLocalMap ,如果threadLocalMap 不为空,
        //则直接更新要保存的变量值,否则创建threadLocalMap,并赋值
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            // 初始化thradLocalMap 并赋值
            createMap(t, value);
}
 
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
}
 
void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
}
 
ThreadLocal.ThreadLocalMap threadLocals = null;
 
static class ThreadLocalMap {
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
 
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
 
        .....
}

从代码可以看到,ThreadLocal调用set方法赋值的时候首先会获取当前线程thread,并获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则更新value值,如果map为空,则实例化threadLocalMap,并将value值初始化。

ThreadLocalMap是ThreadLocal的内部静态类,而它的构成主要是用Entry来保存数据,使用ThreadLocal对象作为key,使用我们设置的value作为value。

Threadlocal是当前线程中属性ThreadLocalMap集合中的某一个Entry的key值,不同的线程之间threadlocal这个key值是一样,但是不同的线程所拥有的ThreadLocalMap是独立的、隔离的,也就是不同的线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而到达了线程间变量隔离的目的,但是在同一个线程中这个value变量引用地址是一样的。如下图所示:

4.2、get方法

public T get() {
        //1、获取当前线程
        Thread t = Thread.currentThread();
        //2、获取当前线程的ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //3、如果map数据不为空,
        if (map != null) {
            //3.1、获取threalLocalMap中存储的值
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为null
        return setInitialValue();
}
 
 
private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
}

4.3、remove方法

 public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
 }

remove方法,直接将ThrealLocal 对应的值从当前Thread中的ThreadLocalMap中删除。

为什么要删除?这涉及到内存泄露的问题。

ThreadLocalMap中的Entry继承了弱应用WeakReference,弱引用的特点是,如果这个对象只存在弱引用,那么在下一次垃圾回收的时候必然会被清理掉,Entry空键(即entry.get())== null)表示该键不再被引用,因此该Entry可以被垃圾回收掉。ThreadLocal其实是与线程绑定的一个变量,如果没有将ThreadLocal内的变量删除(remove)或替换,它的生命周期将会与线程共存。通常线程池中对线程管理都是采用线程复用的方法,在线程池中线程很难结束甚至于永远不会结束,这将意味着线程持续的时间将不可预测,甚至与JVM的生命周期一致。举个例字,如果ThreadLocal中直接或间接包装了集合类或复杂对象,每次在同一个ThreadLocal中取出对象后,再对内容做操作,那么内部的集合类和复杂对象所占用的空间可能会开始持续膨胀。

五、ThreadLocal的应用

ThreadLocal 适用于如下两种场景

1、每个线程需要有自己单独的实例

2、实例需要在多个方法中共享,但不希望被多线程共享

5.1、数据跨层跨方法调用(controller,service, dao)

每个线程内需要保存类似于全局变量的信息(例如在拦截器中获取的用户信息),可以让不同方法直接使用,避免参数传递的麻烦却不想被多线程共享(因为不同线程获取到的用户信息不一样)。最常见的是用 ThreadLocal 保存一些业务内容(用户权限信息、从用户系统获取到的用户名、用户ID 等),这些信息在同一个线程内相同,但是不同的线程使用的业务内容是不相同的。在线程生命周期内,都通过这个静态 ThreadLocal 实例的 get() 方法取得自己 set 的那个对象,避免了将这个对象(如 user 对象)作为参数传递的麻烦。

5.2、数据库连接,处理数据库事务

5.3、Spring使用ThreadLocal解决线程安全问题

一般情况下,只有无状态的Bean才可以在多线程环境下共享,在Spring中,Bean默认的作用域为singleton(单例)。就是因为Spring对一些Bean(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非线程安全的“状态性对象”采用ThreadLocal进行封装,让它们也成为线程安全的“状态性对象”,因此有状态的Bean就能够以singleton的方式在多线程中正常运行。

Web应用划分为展现层、服务层和持久层三个层次,在不同的层中编写对应的逻辑,下层通过接口向上层开放功能调用。在一般情况下,从接收请求到返回响应所经过的所有程序调用都同属于一个线程,如图9-2所示。

这样用户就可以根据需要,将一些非线程安全的有状态的变量以ThreadLocal存放,在同一次请求响应的调用线程中,所有对象所访问的同一ThreadLocal变量都是当前线程所绑定的。

六、使用ThreadLocal注意事项

1、将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值

2、每次使用完ThreadLocal,都调用它的remove()方法,清除数据,防止内存泄露。

标签:map,Thread,ThreadLocalMap,value,解读,ThreadLocal,线程,深度
From: https://www.cnblogs.com/zoufh/p/17876000.html

相关文章

  • vue3使用::v-deep深度选择器不生效
    会出现 ::v-deepusageasacombinatorhasbeendeprecated.Use:deep(<inner-selector>)insteadof::v-deep<inner-selector>.的报错::v-depth用作组合子已被弃用。使用:deep(<内部选择器>)而不是::v-deep<内部选择器>。需要改成:deep(class),示例代码如下:deep(.el-checkbo......
  • 深度学习在工业自动化领域的简析
    原创|文BFT机器人在机器视觉和工业自动化领域,很少有比“深度学习”更引人注目的词汇。大约七年前左右,这个词随着一波庞大的营销炒作而出现,附带着“革命性”和“颠覆性”等形容词。几年后,尘埃落定,深度学习在自动化和制造领域的角色变得更加清晰。当然,深度学习并非魔法,它不能解决......
  • 【深度学习】[传送门] 链接收集帖
    前言  本帖子用于收集一些查阅问题时遇到的有所帮助的帖子。  或因精力不足、或因前人之述备矣、或因不想浏览器收藏夹栏过于冗重,出于上述三个主要原因,本贴简略记录学习过程中所思索过的问题以及对应的帖子或网站,并择而简评。  主要格式为:“问题大致关键词,我的问题描述,帖......
  • games101-2 透视深度插值矫正与抗锯齿分析
    透视深度插值矫正与抗锯齿分析深度插值的差错原因透视深度插值公式推导games101中的错误msaa与ssaa简要定义games101中ssaa的实现games101中msaa的实现深度插值的差错原因当投影的图形与投影的平面不平行时,这时进行透视投影,从上图中可以看出,投影平面上的线段时均匀......
  • 13、深度学习入门:P154、P155、P156、P157、P158、P159
    1、调整权重和偏置以便拟合训练数据的过程称为学习这句话指的是机器学习中模型训练的过程。在训练一个机器学习模型时,我们通常有一个训练数据集,其中包含输入和对应的期望输出。模型的目标是通过学习这些数据中的模式和规律,以便在未见过的数据上做出准确的预测或执行任务。模型学......
  • SAP ABAP RZ11 事务码里 Instance Profile 和 Current Value 等参数值的解读
    首先,让我们了解在SAPABAP系统中通过事务码RZ11查看参数时,涉及的四个重要组件:KernelDefault、DefaultProfile、InstanceProfile和CurrentValue。KernelDefault:含义:KernelDefault表示系统中SAP内核(Kernel)的默认配置参数值。这是SAP系统内核的全局默认设置,通常在SAP系统......
  • 2023 PostgreSQL 数据库生态大会:解读拓数派大数据计算系统及其云存储底座
    11月3日-5日,由中国开源软件推进联盟PostgreSQL分会主办的中国PostgreSQL数据库生态大会在北京中科院软件所隆重举行。大会以”极速进化·融合新生”为主题,从线下会场和线上直播两种方式展开,邀请了数十位院士、教授、高管和社群专家,是2023不容错过的学习机会。拓数派作为PG生态......
  • 拓数派受邀参加由Google举办的“深度探索 LLM / Generative AI的生态与应用”主题活动
    大语言模型(LLM)可谓是当下国内科创界最热门的话题。近日,拓数派创始人兼CEO冯雷(RayVon)受邀参加由Google举办的“深度探索LLM/GenerativeAI的生态与应用”主题活动,与现场嘉宾共话科技行业发展新趋势。图为:活动现场照片在圆桌讨论环节中,冯雷与主持人及几位创业公司高管,进行了一场......
  • RTMP协议学习——Message与Chunk解读
    前言之前通过对抓包数据的学习和分析,对RTMP协议有了一个整体的认知,大致了解了RTMP从建立连接到播放视频的流程,文章请看《RTMP协议学习——从握手到播放》。但是对于RTMP消息传输的载体还没有过多的分析。本文将会就RTMP的传输数据方面,对RTMP协议进行进一步的研究和学习。Messag......
  • 《深度学习入门——自制框架》读书笔记
    1.自动微分step2创建变量的函数#箱子类,存放一个变量数据classVariable: def__init__(self,data): self.data=data#函数类的基类classFunction:#__call__方法是一个特殊的Python方法。#定义了这个方法后,当f=Function()时,就可以通过编写f(...)来......