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

1 概述

        使用InheritableThreadLocal可以在子线程中取得父线程继承下来的值。

2 ThreadLocal类不能实现值的继承

public class Tools {
    public  static ThreadLocal t1 = new ThreadLocal();
}
public class ThreadA extends Thread{
    @Override
    public 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++) {
                if(Tools.t1.get() == null){
                    Tools.t1.set("在main方法中,set的值");
                }
                System.out.println("在main方法中取值 = " + Tools.t1.get());
                Thread.sleep(100);
            }
            Thread.sleep(5000);
            ThreadA a = new ThreadA();
            a.start();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

        因为main线程创建了ThreadA线程,所以main线程时ThreadA的父线程。从运行结果看,由于ThreadA线程并没有继承main线程,所以ThreadLocal并不具有值继承特性,这时就要使用 InheritableThreadLocal类进行替换了。

3 使用InheritableThreadLocal体现值继承特性

        使用InheritableThreadLocal类可以让子线程从父线程中继承值。

public class Tools {
    public static InheritableThreadLocal t1 = new InheritableThreadLocal();
}
public class ThreadA extends Thread{
    @Override
    public 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++) {
                if(Tools.t1.get() == null){
                    Tools.t1.set("在main方法中,set的值");
                }
                System.out.println("在main方法中取值 = " + Tools.t1.get());
                Thread.sleep(100);
            }
            Thread.sleep(5000);
            ThreadA a = new ThreadA();
            a.start();
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

        ThreadA子线程获取的值是从父线程main继承的。

4 值继承特性在源代码中的执行流程

        使用InheritableThreadLocal的确可以实现值继承的特性,那么在JDK的源码中是如何实现的呢?下面按步骤来分析。

        1、首先看一下InheritableThreadLocal类的源码:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    
    protected T childValue(T parentValue) {
        return parentValue;
    }

    
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

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

        在InheritableThreadLocal类的源码中有3个方法,这3个方法都是对父类ThreadLocal中的同名方法进行重写后得到的,因为在源码中并没有使用@override进行标识,所以在初期分析时如果不注意,流程是比较绕的。ThreadLocal类中的三个方法的源码:

    T childValue(T parentValue) {
        throw new UnsupportedOperationException();
    }

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

        从上面的源码中可以看出,ThreadLocal类操作的是threadLocals实例变量,而InheritableThreadLocal类操作的是inheritableThreadLocal实例变量,这是2个变量。

        2、在main方法中使用main线程执行InheritableThreadLocal.set()方法。
 

 public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

        在执行ThreadLocal.java类中的set()方法时,有两个方法已经被InheritableThreadLocal类重写了,分别是getMap()和createMap()。一定要留意,在执行这两个方法时,调用的是InheritableThreadLocal类中重写的getMap和createMap方法。

        3、通过查看InheritableThreadLocal类中的getMap和createMap方法的源码,可以明确一个重要的知识点,那就是不再像Thread类中的ThreadLocal.ThreadLocalMap threadLocals存入数据了,而是在ThreadLocal.ThreadLocalMap inheritableThreadLocals 存入数据,这两个对象在Thread.java类中的声明如下:

/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

        上面的分析步骤明确了一个知识点,就是main线程向inheritableThreadLocals对象写入数据,对象 inheritableThreadLocals就是保存数据的容器,那么子线程如何继承父线程中的inheritableThreadLocals对象的值呢?

        4、这个实现的思路就是在创建子线程ThreadA时,子线程主动引用父线程main里面的inheritableThreadLocals对象值,源码如下:

public
class Thread implements Runnable {

    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }
}

        因为init()方法是被Thread的构造方法调用的,所以在new ThreadA()中,在Thread.java源码内部会自动调用init方法。在init()方法中的最后一个参数inheritThreadLocals 代表当前线程对象是否会从父线程中继承值。因为这个值被永远传入true,所以每次都会继承值。

5 父线程有最新的值,子线程还是旧值:不可变类型

public class Tools {
    public static InheritableThreadLocal t1 = new InheritableThreadLocal();
}
public class ThreadA extends Thread{
    @Override
    public void run(){
        try {
            for (int i = 0; i < 10; i++) {
                System.out.println("在ThreadA线程中取值 = " + Tools.t1.get());
                Thread.sleep(1000);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

}
public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        if(Tools.t1.get() == null){
            Tools.t1.set("此值是在main线程存入的");
        }
        System.out.println("在main线程中取值 = " + Tools.t1.get());
        Thread.sleep(1000);
        ThreadA a = new ThreadA();
        a.start();
        Thread.sleep(5000);
        Tools.t1.set("此值是在main线程 new 放入的");
    }
}

        从运行结果可见,子线程还是持有旧的数据。

6 子线程有最新的值,父线程是旧值:不可变类型

       

public class Tools {
    public static InheritableThreadLocal t1 = new InheritableThreadLocal();
}
public class ThreadA extends Thread{
    @Override
    public void run(){
        try {
            for (int i = 0; i < 10; i++) {
                System.out.println("在ThreadA线程中取值 = " + Tools.t1.get());
                Thread.sleep(1000);
                if(i == 5){
                    Tools.t1.set("此值是在ThreadA线程中写入的");
                    System.out.println("ThreadA已经存在最新值---------");
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        if(Tools.t1.get() == null){
            Tools.t1.set("此值是在main线程中放入的");
        }
        System.out.println("在main线程中取值 = " + Tools.t1.get());
        Thread.sleep(100);
        ThreadA a = new ThreadA();
        a.start();
        Thread.sleep(3000);
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程结束,获取的值 = " + Tools.t1.get());
            Thread.sleep(1000);
        }
    }
}

        可见main线程中存的永远是旧的数据。

7 子线程可以感应对象属性值的变化:可变类型

        前面都是在主、子线程中使用String数据类型做继承特性的实验,如果子线程从父线程继承可变对象数据类型,那么子线程可以得到最新对象中的属性值。

public class UserInfo {
    private String username;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

 

public class Tools {
    public static InheritableThreadLocal<UserInfo> t1 = new InheritableThreadLocal<>();
}
public class ThreadA extends Thread{
    @Override
    public void run(){
        try {
            for (int i = 0; i < 10; i++) {
                UserInfo userInfo = Tools.t1.get();
                System.out.println("在线程a中取值 = " + userInfo.getUsername() +
                        ";" + userInfo.hashCode());
                Thread.sleep(1000);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}
public class Run1 {
    public static void main(String[] args) throws InterruptedException {
        UserInfo userInfo = new UserInfo();
        System.out.println("A userinfo = " + userInfo.hashCode());
        userInfo.setUsername("中国");
        if(Tools.t1.get() == null){
            Tools.t1.set(userInfo);
        }
        System.out.println("在main方法中取值 = " + Tools.t1.get()
         + ";" + Tools.t1.get().hashCode());
        Thread.sleep(100);
        ThreadA a = new ThreadA();
        a.start();
        Thread.sleep(4000);
        Tools.t1.get().setUsername("美国");
    }
}

        程序运行结果就是ThreadA取到userinfo对象的最新属性值。如果在main方法的最后重新放入一个新的UserInfo对象,则ThreadA线程打印的结果永远是中国。这是因为ThreadA永远引用的是中国对应的UserInfo对象,并不是新版美国魅影的UserInfo对象,所以依然符合“父线程有最新的值,子线程还是旧值” 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

功能测试,接口测试,自动化测试,压力测试,性能测试,渗透测试,安全测试,具体是干嘛的?

软件测试是一个广义的概念&#xff0c;他包括了多领域的测试内容&#xff0c;比如&#xff0c;很多新手可能都听说&#xff1a;功能测试&#xff0c;接口测试&#xff0c;自动化测试&#xff0c;压力测试&#xff0c;性能测试&#xff0c;渗透测试&#xff0c;安全测试等&#…

【Python】流畅!一个非常好用的网络数据采集工具!

文章目录 前言一、注册二、初窥三 数据集四 自定义网站网络爬虫总结 前言 你是否曾为获取重要数据而感到困扰&#xff1f;是否因为数据封锁而无法获取所需信息&#xff1f;是否因为数据格式混乱而头疼&#xff1f;现在&#xff0c;所有这些问题都可以迎刃而解。让我为大家介绍…

2023年12月实时获取地图边界数据方法,省市区县街道多级联动【附实时geoJson数据下载】

首先&#xff0c;来看下效果图 在线体验地址&#xff1a;https://geojson.hxkj.vip&#xff0c;并提供实时geoJson数据文件下载 可下载的数据包含省级geojson行政边界数据、市级geojson行政边界数据、区/县级geojson行政边界数据、省市区县街道行政编码四级联动数据&#xff0…

docker的基本管理和概念

docker是什么&#xff1f; docker是开源的应用容器引擎。基于go语言开发的。运行在Linux系统中的开源的轻量级的“虚拟机”。 docker的容器技术可以在一台主机上轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器 docker的宿主机是linux系统。集装箱可以理解为相互…

Java简易版:UDP协议实现群聊

要先 运行服务端&#xff0c;在运行客户端&#xff0c;否则会报错。 服务端&#xff1a; package 二十一章;import java.io.*; import java.net.*; import java.util.ArrayList; public class T{public static ServerSocket server_socket;public static ArrayList<Socket…

EasyX图形化学习

1.EasyX是什么&#xff1f; 是基于Windows的图形编程&#xff0c;给用户提供函数接口&#xff0c;最终函数调用会由Windows的API实现。 注&#xff1a;EasyX只适配 c 。 2.头文件&#xff1a; <easyx.h>---只包含最新的函数 <graphics.h>---包含<easyx.h&g…

Vue3整合Element Plus过程

Vue 是一种流行的JavaScript框架&#xff0c;用于构建交互式和现代化的Web应用程序。Vue 3是Vue框架的最新版本&#xff0c;带来了新特性和改进。而Element Plus是一个基于Vue框架的UI组件库&#xff0c;它提供了丰富的UI组件和样式&#xff0c;能够帮助我们快速构建出漂亮且功…

Towards High-Quality and Efficient Video Super-Resolution via

code:coulsonlee/STDO-CVPR2023: [CVPR2023] Towards High-Quality and Efficient Video Super-Resolution via Spatial-Temporal Data Overfitting (github.com) 随着深度卷积神经网络&#xff08;DNN&#xff09;在计算机视觉的各个领域得到广泛应用&#xff0c;利用DNN的过…

ShellShock(CVE-2014-6271)

漏洞简介 GNU Bash 4.3及之前版本在评估某些构造的环境变量时存在安全漏洞&#xff0c;向环境变量值内的函数定义后添加多余的字符串会触发此漏洞&#xff0c;攻击者可利用此漏洞改变或绕过环境限制&#xff0c;以执行Shell命令。某些服务和应用允许未经身份验证的远程攻击者提…

电商早报 | 12月7日| 阿里巴巴分红179亿,破历史记录

阿里巴巴将派发25亿美元年度股息 12月6日消息&#xff0c;阿里巴巴发布公告&#xff0c;将向截至2023年12月21日香港时间及纽约时间收市时登记在册的普通股持有人和美国存托股持有人&#xff0c;就2023财年首次派发年度股息&#xff0c;金额分别为每股普通股0.125美元或每股美…

【EI会议征稿中】2024年第四届数字信号与计算机通信国际学术会议(DSCC 2024)

2024年第四届数字信号与计算机通信国际学术会议&#xff08;DSCC 2024&#xff09; 2024 4th International Conference on Digital Signal and Computer Communications 第四届数字信号与计算机通信国际会议(DSCC 2024)将于2024年4月12日至14日在中国-香港举行。DSCC 2024旨…

MATLAB机器人对偏导数、雅克比矩阵、行列式的分析与实践

偏导数、雅克比矩阵、行列式都是非常重要的知识点&#xff0c;为了让大家更容易看懂&#xff0c;尽量使用画图来演示。 1、偏导数Partial derivative 对于导数我们已经很清楚了&#xff0c;某点求导就是某点的斜率&#xff0c;也就是这点的变化率。那么偏导数是什么&#xff…

抖去推微信小程序版:短视频矩阵系统视频剪辑+无人直播

短视频矩阵获客工具的出现&#xff0c;给矩阵号的管理及运营带来了极大的便利&#xff0c;大家可以批量生成作品&#xff0c;并且可以实现自动发布&#xff0c;极大的节省了人力物力。 然而&#xff0c;对于中小商家来说虽然很想购买一套这样的系统&#xff0c;但考虑到费用较高…

外贸平台自建站的教程?做海洋建站的好处?

外贸平台自建站怎么做&#xff1f;搭建网站的具体流程有哪些&#xff1f; 作为外贸从业者&#xff0c;借助互联网平台自建站点已经成为推广业务、拓展市场的一种重要手段。海洋建站将为您提供一份详尽的外贸平台自建站的教程&#xff0c;助您在网络空间中展现您的企业魅力。 …

备战2024年1月AMC8美国数学竞赛新方式:刷在线真题集(附资源)

今天是2023年12月7日&#xff0c;距离暂定于2024年1月19日举办的AMC8美国数学竞赛的举办日期还有42天&#xff0c;有志于尽早出国留学&#xff0c;或者小升初冲击名校的孩子们相信已经在如火如荼地利用课余时间上辅导班或者自学。 为了帮助大家提高备考2024年1月份AMC8竞赛的效…

QxOrm 如何自定义主键?

默认情况下QxOrm的主键是long类型自增的&#xff0c;但是有时候我们不想使用这个主键&#xff0c;想使用比如string类型的主键。 可以使用QX_REGISTER_PRIMARY_KEY宏定义另一种类型&#xff08;例如&#xff0c;QString 类型&#xff09;的唯一 id&#xff08;主键&#xff09…

第 7 部分 — 增强 LLM 安全性的策略:数学和伦理框架

一、说明 增强大型语言模型 (LLM) 安全性的追求是技术创新、道德考虑和实际应用的复杂相互作用。这项努力需要一种深入而富有洞察力的方法&#xff0c;将先进的数学模型与道德原则和谐地融合在一起&#xff0c;以确保LLM的发展不仅在技术上稳健&#xff0c;而且在道德上合理且对…

实现简易的一对一用户聊天

服务端 package 一对一用户;import java.awt.BorderLayout; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Vector…

Linux 存储管理

内容概述 磁盘结构分区类型管理分区管理文件系统挂载设备管理swap空间&#xff08;用来缓解内存空间不足情况&#xff09;RAID 管理LVM管理LVM快照 1 磁盘结构 1.1 设备文件 块设备文件&#xff1a;数据的访问单位是块Block&#xff0c;一个块的IO 字符设备文件&#xff1a…

文章解读与仿真程序复现思路——中国电机工程学报EI\CSCD\北大核心《计及需求响应与火–储深度调峰定价策略的电力系统双层优化调度》

这个标题似乎涉及到电力系统的双层优化调度问题&#xff0c;并考虑了两个关键方面&#xff1a;需求响应和火–储深度调峰定价策略。 电力系统双层优化调度&#xff1a;这指的是在电力系统中进行优化调度的过程。双层优化可能意味着系统具有两个层次的决策过程&#xff0c;通常是…