JUC多并发编程 ThreadLocal

news2025/1/20 6:01:50
  • ThreadLocal 提供线程局部变量。这些变量与正常的变量不同, 因为每一个线程在访问 ThreadLocal 实例的时候(通过其 get 或 set 方法) 都有自己的,独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段, 使用它的目的是希望将状态(例如, 用户ID或事务ID) 与线程关联起来。
  • 主要解决了让每个线程绑定自己的值, 通过使用 get() 和 set () 方法, 获取默认值或将其值更改为当前线程所存副本的值,从而避免了线程安全问题

实例编码:

import java.util.Random;
import java.util.concurrent.TimeUnit;

class House{
    int saleCount = 0;
    public synchronized  void saleHouse() {
        ++ saleCount;
    }
    ThreadLocal<Integer> saleVolume = ThreadLocal.withInitial(()->0);

    public void saleVolumeByThreadLocal() {
        saleVolume.set(1+saleVolume.get());
    }

}

public class ThreadLocalDemo {
    public static void main(String[] args) {
        House house = new House();
        for (int i = 1; i <= 5; i++) {
            new Thread(()-> {
                int size = new Random().nextInt(5) + 1;
                //System.out.println(size);
                try {
                    for (int j = 1; j <= size; j++) {
                        house.saleHouse();
                        house.saleVolumeByThreadLocal();
                    }
                    System.out.println(Thread.currentThread().getName() + "\t" + "号销售卖出:" + house.saleVolume.get());
                } finally {
                    // 避免内存泄露
                    house.saleVolume.remove();
                }
            }, String.valueOf(i)).start();
        }

        try{ TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println(Thread.currentThread().getName() + "\t" + "总计卖出多少套:" + house.saleCount);

    }
}

线程池的使用:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class MyData{
    ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()->0);
    public void add(){
        threadLocal.set(1+threadLocal.get());
    }
}
public class ThreadLocalDemo2 {
    public static void main(String[] args) {
        MyData myData = new MyData();
        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        try{
            for (int i = 0; i < 10; i++) {
                // 线程池 会复用

                threadPool.submit(()-> {
                    try {
                        Integer beforeInt = myData.threadLocal.get();
                        myData.add();
                        Integer afterInt = myData.threadLocal.get();
                        System.out.println(Thread.currentThread().getName() + "\t" + "beforeInt:" + beforeInt + "\t" + "afterInt:" + afterInt);
                    }finally {
                        myData.threadLocal.remove();
                    }
                });
            }
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            threadPool.shutdown();
        }

    }
}

ThreadLocal 底层源码分析

Thread、ThreadLocal 和ThreadLocalMap 关系:

  • threadLocalMap 是一个以 threadLocal 实例的 key,任意对象为 value 的 Entry 对象,当我们为threadLocal 变量赋值,实际上就是以当前 threadLocal 实例为 key, 值为 value 的 Entry 往这个 threadLocalMap 中存放
  • ThreadLocalMap 从字面上就可以看出这时一个保存 ThreadLocal 对象的 map(其实是以 ThreadLocal 为 Key), 不过是经过了两层包装的 ThreadLocal 对象
  • JVM 内部维护了一个线程版的 Map<ThreadLocal,Value>(通过 ThreadLocal 对象的 set 方法, 结果把 ThreadLocal 对象自己当作 Key, 放进了 ThreadLocalMap), 每个线程要用到这个 T 的时候,用当前的线程去 Map里面获取, 通过这样让每个线程都拥有自己的独立的变量,人手一份,竞争条件被彻底消除,在并发模式下是绝对安全的变量

ThreadLocal 内存泄漏:

public class T1 {
    volatile boolean flag;

    public static void main(String[] args) {
        ThreadLocal<String> t1 = new ThreadLocal<>();
        t1.set("hello threadlocal");
        t1.get();
    }
}
  • 不在会被使用的对象或者变量占用的内存不能被回收
  • 当 function01 方法执行完毕后,栈帧销毁强引用 t1 也就没有了。但是此线程的 ThreadLocalMap 里某个 entry 的 key 引用还指向这个对象,若这个 key 引用是强引用, 就会导致 key 指向的 ThreadLocal 对象及 v 指向的对象不能被 gc 回收,造成内存泄漏。若这个 key 引用是 弱引用就大概率会减少内存泄漏的问题。就可以使 ThreadLocal 对象在方法执行完毕后顺利被回收且 Entry 的 key 引用指向为 null。
  • 虽然弱引用,保证了 key 指向的 ThreadLocal 对象能被及时回收, 但是 v 指向的 value 对象是需要 ThreadLocalMap 调用 get,set 时发现 key 为 null 时才会去回收整个 entry、value,因此弱引用不能 100% 保证内存不泄漏. 我们要在不使用某个 ThreadLocal 对象后,手动调用 remove 方法来删除它,尤其是在线程池中,不仅仅是内存泄漏的问题,因为线程池中线程是重复使用的,意味着这个线程的 ThreadLocalMap 对象也是重复使用的,如果不手动调用 remove 方法, 那么后面的线程就有可能获取到上个线程遗留下来的 value 值, 造成 bug

 

 

引用类型:

  • Java 技术允许使用 finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作

import java.lang.ref.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

class MyObject {
    @Override
    protected void finalize() throws Throwable {
        // 通常目的实在对象被不可撤销地丢弃之前执行清理操作
        System.out.println("------ invoke finalize method~!!!");


    }
}
public class ReferenceDemo {
    public static void main(String[] args) {
        MyObject myObject = new MyObject();

        ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>();
        PhantomReference<MyObject> phantomReference = new PhantomReference<>(myObject, referenceQueue);

        System.out.println(phantomReference.get());
        List<byte[]> list = new ArrayList<>();
        new Thread(()-> {
            while(true) {
                list.add(new byte[1*1024*1024]);
                try{ TimeUnit.MILLISECONDS.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); }
                System.out.println(phantomReference.get() + "\t" + "list add ok");
            }
        },"t1").start();

        new Thread(()-> {
            while(true) {
                Reference<? extends  MyObject> reference = referenceQueue.poll();
                if (reference != null) {
                    System.out.println("----- 有虚对象回收加入队列");
                    break;
                }
            }
        },"t2").start();
    }
    /**
     * 弱引用
     */
    public static void weak() {
        WeakReference<MyObject> weakReference = new WeakReference<MyObject>(new MyObject());
        System.out.println("gc before 内存够用:" + weakReference.get());

        System.gc();
        try{ TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("gc after 内存够用:" + weakReference.get());
    }
    /**
     * 软引用
     */
    public static void soft() {
        SoftReference<MyObject> softReference = new SoftReference<>(new MyObject());
        System.out.println("---- softReference: "+ softReference.get());

        System.gc();
        try{ TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("gc after 内存够用:" + softReference.get());
        // VM option 启动时配置 -Xms10m -Xmx10m
        try {
            byte[] bytes = new byte[50 * 1024 * 1024];
        }catch (Exception e) {
            e.printStackTrace();
        }finally {

        }
        System.out.println("gc after 内存不够用:" + softReference.get());

    }

    /**
     * 强引用
     */
    public static void strong() {
        MyObject myObject = new MyObject();
        System.out.println("gc before:" + myObject);

        myObject = null;
        System.gc(); // 人工开启GC,一般不用

        System.out.println("gc after:" + myObject);
    }
}

强引用:

  • 当内存不足, JVM 开始垃圾回收,对于强引用的对象, 就算是出现了 OOM 也不会对该对象进行回收
  • 当一个对象被强引用变量引用时, 它处于可达状态,即使该对象以后永远不会被用到,JVM 也不会回收
  • 对于一个普通对象,如果没有其他的引用关系,只要超过了引用的作用域或者显示地将相应(强)引用赋值为null, 一般认为就是可以被垃圾收集的了(还要看具体的策略)

软引用:

  • 是一种相对强引用弱化了一些的引用,需要用 java.lang.ref.SoftReference 来实现, 可以让对象豁免一些垃圾回收
  • 当系统内存充足时它不会被回收,当系统内存不足时它会被回收。
  • 通常用在对内存敏感的程序中,比如高速缓存就用到了软引用,内存够用的时候就保留,不够用就回收

弱引用:

  • 需要用 java.lang.ref.weakReference 类来实现, 它比软引用的生存期更短
  • 对于只有弱引用的对象来说,只要垃圾回收机制一运行,不管 JVM 的内存空间是否足够,都会回收该对象占用的内存

虚引用:

  • 必须和引用队列 ReferenceQueue 联合使用,需要java.lang.ref.PhantomReference 类来实现。虚引用并不会决定对象的生命周期。如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收,它不能单独使用也不能通过它访问对象。
  • 虚引用的主要作用是跟踪对象被垃圾回收的状态。仅仅是提供一种确保对象被 finalize 以后,做某些事情的通知机制。PhantomReference 的 get方法总是返回 null, 因此无法访问对象的引用对象
  • 设置虚引用关联对象的唯一目的, 就是在这个对象被收集器回收的时候收到一个系统通知或者后续添加进一步处理,用来实现比 finalize 机制更灵活的回收操作

总结

  • ThreadLocal 并不解决线程间共享数据的我呢提
  • ThreadLocal 适用于变量在线程间隔且方法间共享的场景
  • ThreadLocal 通过隐式的在不同线程内创建独立实例副本避免了实例线程安全的问题
  • 每个线程有一个只属于自己的专属 Map 并维护了 ThreadLocal 对象与具体实例的映射,该Map 由于只被持有它的线程访问, 故不存在线程安全以及锁的问题
  • ThreadLocalMap 的 Entity 对 ThreadLocal 的引用为弱引用,避免了 ThreadLocal 对象无法被回收的问题
  • 都会通过 expungeStaleEntry,CleanSomeSlots, replaceStaleEntry 这三个方法回收键为 null 的 Entity 对象的值(即为具体实例) 以及 Entry 对象本身从而防止内存泄漏,属于安全加固的方法

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

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

相关文章

【OJ比赛日历】快周末了,不来一场比赛吗? #04.29-05.05 #16场

CompHub 实时聚合多平台的数据类(Kaggle、天池…)和OJ类(Leetcode、牛客…&#xff09;比赛。本账号同时会推送最新的比赛消息&#xff0c;欢迎关注&#xff01; 更多比赛信息见 CompHub主页 或 点击文末阅读原文 以下信息仅供参考&#xff0c;以比赛官网为准 目录 2023-04-29&…

SQL学习日记

目录 一、数据定义&#xff08;create&#xff0c;alter&#xff0c;drop&#xff09; 1.1数据类型 1.2定义基本表&#xff08;create&#xff0c;alter&#xff0c;drop&#xff09; 1.3约束 1.3.1主键约束 1.3.2外码约束 ​编辑 补充CASCADE 关键字 1.3.3Check约束 …

自定义类型:结构体

ok&#xff0c;兄弟们&#xff0c;今天来写关于自定义类型的博客&#xff0c;先来看结构体。 结构体 结构是一些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量。 1.结构体类型的声明 struct tag {member-list; }variable-list; 以上就是结构…

学习 Python 之 Pygame 开发魂斗罗(十六)

学习 Python 之 Pygame 开发魂斗罗&#xff08;十六&#xff09; 完成最终的魂斗罗1. 创建Sound类2. 添加背景音乐3. 添加玩家发射子弹音效4. 增加击中boss要害音效5. 击中敌人音效6. 加入进场动画7. 解决玩家掉出地图死亡问题8. 完善玩家游戏失败函数9. 总结 完成最终的魂斗罗…

道达天际首次亮相军博会,“天网融合”引爆全场

4月23日,第十届中国指挥控制大会暨第八届中国(北京)军事智能技术装备博览会(军博会)落下帷幕。北京道达天际科技股份有限公司(简称道达天际)携DAODAJ2天网情报产品体系首次亮相,全面展示天网融合技术、产品、行业解决方案等成果,最新技术应用备受现场观众瞩目,“天网融合”创新…

性能优化对于Android程序员的重要性,看完你就明白

前言 相信我们都使用过Android手机&#xff0c;然后在使用的过程中经常会遇到手机卡顿&#xff0c;应用闪退&#xff0c;画面不流畅等问题&#xff1b;正因为如此&#xff0c;就导致用户体验非常差&#xff0c;最后选择不再使用Android手机。对此&#xff0c;很多公司对Androi…

【软考备战·希赛网每日一练】2023年4月27日

文章目录 一、今日成绩二、错题总结第一题第二题第三题第四题 三、知识查缺 题目及解析来源&#xff1a;2023年04月27日软件设计师每日一练 一、今日成绩 二、错题总结 第一题 解析&#xff1a; ADSL Modem 上网拨号方式有3种&#xff0c;即 专线方式&#xff08;静态IP&#…

半导体运动台基于dsp+fpga+ad+endac的高速数据采集FPGA设计(二)

4 系统 FPGA 程序的设计 4.1 设计方法及逻辑设计概述 4.1.1 开发环境与设计流程 Quartus II 是 Altera 公司综合开发工具&#xff0c;它集成了 FPGA/CPLD 开发过程中所设计 的所有工具和第三方软件接口&#xff0c;支持多时钟分析&#xff0c; LogicLock 基于块的…

Linux内核阅读自学精简教程目录(必读)

学习Linux内核需要一定的计算机基础知识&#xff0c;包括操作系统&#xff0c;计算机网络等。 以下是学习Linux内核的步骤&#xff1a; 了解Linux内核的基本概念和架构&#xff0c;学习Linux内核源代码的组成和结构。学习C语言和汇编语言&#xff0c;这是深入理解Linux内核的…

ssh设置别名 ,登录

1. ssh设置别名&#xff1b; 可以使用alias命令来给ssh命令起别名&#xff0c;例如&#xff1a; alias mysshssh这样就可以使用myssh命令来代替ssh命令了。如果想要永久生效&#xff0c;可以将上述命令加入到~/.bashrc文件中。 &#xff0c;如果没有 ~/.bashrc 此文件&#…

第十八章 迭代器模式

文章目录 前言一、迭代器模式基本介绍二、迭代器模式应用实例完整代码Department 系ComputerCollegeIterator 计算机学院迭代器InfoColleageIterator 信息工程学院迭代器College 学院接口ComputerCollege 计算机学院InfoCollege 信息工程学院OutPutImpl 操作迭代器Clint 测试 三…

一个恶意下载器的逆向分析

Die查壳, 发现没有加壳, 是使用VC编写的64位程序 丢入VT用杀毒引擎和沙箱扫, 爆红基本可以确定其属于恶意软件: 查看其PE节区发现其包含了资源节, 内部可能藏有隐藏模块 查看一下这个程序导入的dll中发现了如下特别的地方 并且其还使用了LoadLibrary和GetProcAddre…

使用aardio写一个基于pyocd的单片机下载器

1 新建工程 最开始本来是打算调用pyocd 的python api的&#xff0c;但是一个是内嵌包一直安装出问题&#xff0c;一个是考虑到本地pack不想重复安装和管理&#xff0c;于是就转做pyocd的前端了&#xff0c;也就是直接调用pyocd&#xff0c;根据返回数据解析&#xff0c;然后执…

NumPy之矩阵、向量、线性代数等的操作

NumPy之矩阵、向量、线性代数 NumPy矩阵和向量矩阵向量创建向量创建矩阵访问元素转置矩阵矩阵加减乘除矩阵向量乘法矩阵求逆矩阵的迹向量点积向量范数 NumPy线性代数计算矩阵乘积计算矩阵的逆解线性方程组 NumPy矩阵和向量 矩阵 在NumPy中&#xff0c;矩阵可以看作是一个二维数…

【Django】Django ORM Cookbook--20230427

英文版http://books.agiliq.com/projects/django-orm-cookbook/en/latest/ 中文版https://django-orm-cookbook-zh-cn.readthedocs.io/zh_CN/latest/query.html 查询和筛选 1. 如何查看Django ORM查询集的原生SQL&#xff1f; >>> queryset Event.objects.all() &…

JVM内存模型和结构

JVM是Java Virtual Machine&#xff08;Java虚拟机&#xff09;的缩写&#xff0c;JVM是一个虚构出来的计算机&#xff0c;有着自己完善的硬件架构&#xff0c;如处理器、堆栈等。 为什么需要JVM&#xff1f; Java语言使用Java虚拟机屏蔽了与具体平台相关的信息&#xff0c;使…

QT笔记——第三方开源库停靠窗口类似QDockWidget

我们想要一个类似于Visual Studio 2019的dockwidget 停靠窗口 一个开源库&#xff1a;类似于Visual Studio 2019 dockwidget 的开源库 下载&#xff0c;解压下来 使用vs qt 插件打开src文件夹下的 src.pro 生成如下&#xff1a; 我们来运行它的例子&#xff1a; 使用vs qt …

【Vue工程】001-Vite 创建 Vue-TypeScript 项目

【Vue工程】001-Vite 创建 Vue-TypeScript 项目 文章目录 【Vue工程】001-Vite 创建 Vue-TypeScript 项目一、环境二、创建项目1、pnpm 创建 Vite 项目2、设置项目名3、选择vue4、选择 TypeScript5、创建完成6、安装与启动7、访问 http://localhost:5173/8、默认生成的项目结构…

LeetCode0718.最长重复子数组 Go语言AC笔记

时间复杂度&#xff1a;O(n)&#xff0c;空间复杂度&#xff1a;O(n) 解题思路 动态规划思想。令dp[i][j]表示两数组以nums1[i]和nums2[j]为起始元素的公共前缀最大长度&#xff0c;所以如果nums1[i]和nums2[j]元素相同&#xff0c;那么dp[i][j]dp[i1][j1]&#xff0c;否则dp[…

Baumer工业相机堡盟工业相机如何联合BGAPI SDK和OpenCVSharp合并偏振相机4个角度的图像并显示(C#)

Baumer工业相机堡盟工业相机如何联合BGAPI SDK和OpenCVSharp合并偏振相机4个角度的图像并显示&#xff08;C#&#xff09; Baumer工业相机Baumer工业相机偏振相机的技术背景代码案例分享1&#xff1a;引用合适的类文件2&#xff1a;回调函数里联合BGAPI SDK和OpenCVSharp合并偏…