Java多线程技术二:线程间通信——ThreadLocal的使用

1 概述

        变量值的共享可以使用public static 的声明方式,所有的线程都是用同一个public static变量,那如果想实现每一个线程都有自己的变量该如何解决呢?JDK提供的ThreadLocal就派上用场了。

        ThreadLocal类主要的作用就是将数据放入当前线程对象中的Map里,这个Map类是Thread类的实例变量。ThreadLocal类自己不管理也不存储任何数据,它只是数据和Map之间的中介和桥梁,通过ThreadLocal将数据放入Map中,执行流程如下:

数据——>ThreadLocal——>currentThread()——>Map

        执行后每个线程中的Map就存储自己的数据,Map中的key存储的是ThreadLocal对象,value就是存储的值,说明ThreadLocal和值之间是一对一的关系,一个ThreadLocal对象只能关联一个值。每个线程中Map的值只对向前线程可见,其他线程不可以访问当前线程对象中Map的值。内存结构如下:

2 get()方法与null

        如果从未在Thread中的Map存储 ThreadLocal对象对应的值,则get()方法返回null。

public class Run1 {public static ThreadLocal t1 = new ThreadLocal();public static void main(String[] args) {if(t1.get() == null){System.out.println("从未放过值");t1.set("第一次放的值");}System.out.println(t1.get());System.out.println(t1.get());}
}

        ThreadLocal类解决的是变量在不同线程中的隔离性,也就是不同线程拥有自己的值,不同线程中的值是可以通过ThreadLocal类进行保存的。

3 ThreadLocal类存取数据流程分析

        运行测试程序:

public class Test {public static void main(String[] args) {ThreadLocal local = new ThreadLocal();local.set("value");System.out.println(local.get());}
}

        从JDK源码角度来分析一下ThreadLocal类执行存取操作的流程。

        首先看一下数据如何存入到ThreadLocal中的。

        (1)执行ThreadLocal.set("value")代码时,ThreadLocal代码如下:

public void set(T value) {//对象t就是main线程Thread t = Thread.currentThread();//从main线程中获取ThreadLocalMap ThreadLocalMap map = getMap(t);if (map != null) {//如果map不等于null,则set操作map.set(this, value);} else {//如果map等于null,则先执行创建,在执行setcreateMap(t, value);}}

        (2)ThreadLocalMap map = getMap(t); 源码如下:

ThreadLocalMap getMap(Thread t) {//参数t就是前面传入的main线程return t.threadLocals;//返回main线程中threadLocals变量对应的ThreadLocalMap对象}

        对象threadLocals数据类型就是ThreadLocal.ThreadLocalMap,变量threadLocals是Thread类中的实例变量。

        (3)取得Thread中的ThreadLocal.ThreadLocalMap后,根据map对象值是不是null来决定是否对其执行set或create and set操作。

        (4)createMap()方法的功能是创建一个新的ThreadLocalMap,并在这个新的ThreadLocalMap里存储数据,ThreadLocalMap中的key就是当前ThreadLocal对象,值就是传入的value,createMap()方法的源码如下:

void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);}

        在实例化ThreadLocalMap的时候,向构造方法传入thi和firstValue,其中,this就是当前ThreadLocal对象,firstValue就是调用ThreadLocal对象时set()方法传入的参数值。

new ThreadLocalMap(this, firstValue)的源码是:

ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);table[i] = new Entry(firstKey, firstValue);size = 1;setThreshold(INITIAL_CAPACITY);}

        在源码中可以发现,将ThreadLocal对象与firstValue封装进Entry对象中,并放入table[]数组。

        再看一下get()的执行流程。

        (1)当执行 local.get() 代码时,ThreadLocal.get()源码如下:

    public T get() {Thread t = Thread.currentThread();//t 就是main线程ThreadLocalMap map = getMap(t);//从main线程中获取ThreadLocalMap if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);//如果map不等于null,获取Entry对象if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}

        总结:上面的几个步骤就是set和get的执行流程,比较麻烦。为什么不能直接向Thread类中的ThreadLocalMap对象存取数据呢?这是无法实现的,原因参考下面代码:

ThreadLocal.ThreadLocalMap threadLocals = null;

        变量 threadLocals 默认是包级访问,所以不能从外部直接访问该变量,也没有对应的get和set方法,只有用同一个包中的类可以访问threadLocals变量,而ThreadLocal和Thread恰好在同一个包中(都在java.lang包下)。

4 验证线程变量的隔离性

        本节将实现通过使用ThreadLocal在每个线程中存储自己的私有数据。

public class Tools {public static ThreadLocal t1 = new ThreadLocal();
}
public class MyThreadA extends Thread{@Overridepublic void run(){try {for (int i = 0; i < 10; i++) {Tools.t1.set("A: " +(i+1) );System.out.println("A get:" + Tools.t1.get());int sleepValue = (int)(Math.random() * 10000);Thread.sleep(sleepValue);}}catch (InterruptedException e){e.printStackTrace();}}
}
public class MyThreadB extends Thread{@Overridepublic void run(){try {for (int i = 0; i < 10; i++) {Tools.t1.set("B: " +(i+1) );System.out.println("B get:" + Tools.t1.get());int sleepValue = (int)(Math.random() * 10000);Thread.sleep(sleepValue);}}catch (InterruptedException e){e.printStackTrace();}}
}
public class Run1 {public static void main(String[] args) throws InterruptedException {MyThreadA a = new MyThreadA();MyThreadB b = new MyThreadB();a.start();b.start();for (int i = 0; i < 10; i++) {Tools.t1.set("main: " +(i+1) );System.out.println("main get:" + Tools.t1.get());int sleepValue = (int)(Math.random() * 10000);Thread.sleep(sleepValue);}}
}

        控制台输出的结果表示通过ThreadLocal向每个线程存储自己的私有数据,虽然3个线程都向t1存放数据,但是每个线程仅能取出自己的数据,不能取出其他线程存放的数据 。

5 解决get()返回null的问题

        新建ThreadLocalExt.java,继承ThreadLocal类,并覆盖 initialValue() 方法

public class ThreadLocalExt extends ThreadLocal{@Overrideprotected Object initialValue(){return "我是默认值,第一次get不再为null";}
}

         覆盖initialValue()方法具有初始值,因为ThreadLocal.java中的initialValue方法默认返回值就是null,所以要在子类中重写。源码如下:

protected T initialValue() {return null;}
public class Run1 {public static ThreadLocalExt t1 = new ThreadLocalExt();public static void main(String[] args) {if(t1.get() == null){System.out.println("没有存放过值");t1.set("第一次存放值");}System.out.println(t1.get());System.out.println(t1.get());}

6 验证重写initialValue()方法的隔离性

public class Tools {public  static ThreadLocalExt t1 = new ThreadLocalExt();
}

public class ThreadLocalExt extends ThreadLocal{@Overrideprotected Object initialValue() {return new Date().getTime();}
}
public class ThreadA extends Thread{@Overridepublic void run(){try {for (int i = 0; i < 10; i++) {System.out.println("在线程ThreadA中取值 = " + Tools.t1.get());Thread.sleep(100);}}catch (InterruptedException e){e.printStackTrace();}}
}
public class Run1 {public static void main(String[] args) {try {for (int i = 0; i < 10; i++) {System.out.println("在Main线程中取值 = " + Tools.t1.get());}Thread.sleep(2000);ThreadA threadA = new ThreadA();threadA.start();}catch (InterruptedException e){e.printStackTrace();}}
}

7 使用remove()方法的必要性

        ThreadLocalMap中的静态内置类Entry是弱引用类型,源码如下:

static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}

         弱引用的特点是,只要垃圾回收器扫描时发现弱引用的对象,就不管内存是否足够,都会回收弱引用的对象。也就是只要执行gc操作,ThreadLocal对象就会立即销毁,代表key的值ThreadLocal对象会随着gc操作而销毁,释放内存空间,但value值却不会随着gc操作而销毁,这会出现内存溢出。如果对象数量过多,对于ThreadLocalMap类中不用的数据使用ThreadLocal类的remove方法进行清除,实现业务对象的垃圾回收,释放内存。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/198497.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FreeRTOS下的“Hello World”

什么是实时操作系统&#xff08;RTOS&#xff09; 实时操作系统&#xff08;RTOS&#xff09;是一种专为实时应用程序设计的操作系统。实时应用程序需要在特定时间内做出预测的响应&#xff0c;因此RTOS专注于提供对时间约束的强调&#xff0c;以确保系统能够满足实时性能要求…

web前端开发HTML/css用户登录界面

代码&#xff1a; <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns"http://www.w3.org/1999/xhtml"> <head> <meta http-equi…

react学习--Redux的使用

Redux 的核心思想是通过一个单一的状态树来管理应用的状态&#xff0c;状态的修改通过纯函数&#xff08;reducers&#xff09;来进行&#xff0c;从而使状态变化可追踪和可预测。 1、安装 Redux&#xff1a; 在项目中安装 Redux 库和 React-Redux&#xff08;用于 React 绑定…

神经网络常用归一化和正则化方法解析(一)

&#x1f380;个人主页&#xff1a; https://zhangxiaoshu.blog.csdn.net &#x1f4e2;欢迎大家&#xff1a;关注&#x1f50d;点赞&#x1f44d;评论&#x1f4dd;收藏⭐️&#xff0c;如有错误敬请指正! &#x1f495;未来很长&#xff0c;值得我们全力奔赴更美好的生活&…

Diary11-Word文字

Word文字 一.标尺与标记 1.标尺 1.1标尺的打开方式 标尺&#xff1a;视图->标尺(选中即可) 1.2标尺的相关操作 左边的倒三角&#xff1a;【首行缩进】->第一行与左边的间距 左边的正三角&#xff1a;【悬挂缩进】->除了第一行之外的行与左边的间距 右边的正三角…

Win环境中安装Jenkins指南

目录 安装Java环境 下载并安装Jenkins Jenkins版本 启动Jenkins 如何删除Jenkins 安装Java环境 访问 Oracle官方网站 下载并安装JDK 安装完成后&#xff0c;设置系统环境变量 JAVA_HOME 到你的 JDK 安装路径&#xff0c;并将 %JAVA_HOME%\bin 添加到系统 PATH 中。 下载…

Apollo新版本Beta技术沙龙参会感受:未来的自动驾驶之旅

Apollo新版本Beta技术沙龙参会感受&#xff1a;未来的自动驾驶之旅 &#x1f697;&#x1f4a1; 文章目录 Apollo新版本Beta技术沙龙参会感受&#xff1a;未来的自动驾驶之旅 &#x1f697;&#x1f4a1;摘要引言正文&#x1f4cd; 参会流程介绍&#x1f31f; 参会收获&#x1…

「Verilog学习笔记」任意小数分频

专栏前言 本专栏的内容主要是记录本人学习Verilog过程中的一些知识点&#xff0c;刷题网站用的是牛客网 timescale 1ns/1nsmodule div_M_N(input wire clk_in,input wire rst,output wire clk_out );parameter M_N 8d87; parameter c89 8d24; // 8/9时钟切换点parameter di…

封装时间轴组件 timeline

要求时间轴的点展示进度百分比&#xff0c;线也根据进度不同展示不同长度的颜色 实现效果&#xff1a; 使用的组件库是vant的circle 子组件&#xff1a; <template><div class"m-timeline-area" :style"width: ${width}px"><div class&qu…

python读取txt,csv, 等不同格式的文件——(待完善)

python 读取txt文件 1.python读取txt文件&#xff08;多种方法&#xff09;

#名词区别篇:事件流事件委托addEventListener白屏时间首屏时间

事件流 事件捕获 —从外到内 到达目标 事件冒泡—从内到外 事件委托 定义&#xff1a;当有大量子元素触发事件时&#xff0c;将事件监听器绑定在父元素进行监听&#xff0c;此时数百个事件监听器变成一个监听器。 e.currentTarget 与 e.target 有何区别 Event 接口的只读…

前端利用JS获取最近1、3、5、n年的开始时间和结束时间

1.获取最近1年的开始时间和结束时间 function getLastYearStartAndEnd() {const currentDate new Date();const currentYear currentDate.getFullYear();const currentMonth currentDate.getMonth() 1;const lastYearStart new Date(currentYear - 1, currentMonth, 1);c…

聊聊 Jetpack Compose 的 “状态订阅自动刷新” -- 你真的了解重组吗?

Jekpack Compose “状态订阅&自动刷新” 系列&#xff1a; 【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” - - MutableState/mutableStateOf 】 【 聊聊 Jetpack Compose 的 “状态订阅&自动刷新” - - remember 和重组作用域 】 【 聊聊 Jetpack Compose 的 …

Spring Cloud 配置 Druid(二)

不废话&#xff0c;直接上代码&#xff0c; Nacos搭建的微服务&#xff0c;可以看Spring Cloud 配置 Nacos&#xff08;一&#xff09;-CSDN博客 一&#xff0c;pom文件 spring-cloud-starter-alibaba-nacos-discovery 和 spring-cloud-starter-openfeign 都是基于spring-cl…

Apollo新版本Beta技术沙龙的参会感受

Apollo新版本Beta技术沙龙的参会感受 Apollo新版本Beta技术沙龙的参会感受摘要 &#x1f697;&#x1f310;参会流程 &#x1f5d3;️展厅参观/展厅讲解 &#x1f3e2;进入百度Apollo未来驾驶汽车5G云代驾的神奇签到 &#x1f4dd;Apollo新版本Beta整体介绍 &#x1f680;技术分…

C语言:用递归的方法求斐波那契数列:1,1,2,3,5,8,……的前40个数

分析&#xff1a; 首先&#xff0c;在代码的起始部分&#xff0c;包含<stdio.h>头文件&#xff0c;这个头文件提供了输入和输出的函数。 然后&#xff0c;定义了四个变量&#xff1a;f、f1、f2和i。f1和f2是斐波那契数列的前两个数字&#xff0c;初始化为1。f是当前计…

qt使用wimlib-imagex,做windows系统备份还原

wimlib-imagex是个第三方工具&#xff0c;可对系统映像进行操作&#xff0c;下载地址&#xff1a; https://wimlib.net/downloads/index.html 程序主要用到以下这两个文件&#xff1a;libwim-15.dll和wimlib-imagex.exe wimlib-imagex.exe的调用命令参数&#xff0c;可以通过…

【Docker】资源配额及私有镜像仓库

资源配额及私有镜像仓库 一、Docker资源配额1.1、控制cpu1.1.1、cpu份额控制1.1.2、core核心控制1.1.3、配额控制参数的混合使用 1.2、控制内存1.3、控制IO1.4、资源释放 二、Docker私有镜像仓库Harbor2.1、Harbor简介2.2、为Harbor自签发证书【1】生成ca证书【2】生成域名的证…

输出完全二叉树中某个结点的双亲和所有子孙。假设完全二叉树的顺序存储在一维数组A[n]中。

思路&#xff1a; 首先定义两个函数&#xff0c;getParent函数用于获取指定结点的双亲结点的索引&#xff0c;printDescendants函数用于输出指定结点的所有子孙。然后在main函数中&#xff0c;创建表示完全二叉树的数组A&#xff0c;并针对指定结点索引进行相关操作&#xf…

HOST文件被挟持,无法上网,如何解决。

问题&#xff1a; 晚上开机&#xff0c;突然发现无法联网&#xff0c;提示网络异常 解决&#xff1a; 首先网络诊断&#xff0c;host文件被劫持&#xff0c;修复后&#xff0c;仍然不行。 然后测试手机热点&#xff0c;发现仍然无法联网 尝试用火绒修复&#xff0c;无果。 所有…