案例练习理解ThreadLocal以及应用场景

news2024/9/24 21:16:30

目录

案例练习

应用场景


ThreadLocal:用来解决多线程程序下并发问题,通过为每一个线程创建一份共享变量的副本保证线程之间的变量的访问和修改互不影响。

案例练习

1.三个销售卖小米SU7,求他们的总销售。使用CountDownLatch维护三个线程

package threadlocalTest;

import java.util.Random;
import java.util.concurrent.CountDownLatch;

//资源类,本门店有三个销售(三个线程)卖小米SU7
class SU7 {
    private int saleTotal;
    public synchronized void saleTotal(){
        saleTotal++;
    }

    public int getSaleTotal() {
        return saleTotal;
    }

    public void setSaleTotal(int saleTotal) {
        this.saleTotal = saleTotal;
    }
}
//需求一:求三个销售的总体销售额
public class ThreadLocalDemo1 {
    public static void main(String[] args) throws InterruptedException {
        SU7 su7 = new SU7();
        CountDownLatch countDownLatch = new CountDownLatch(3);

        for (int i = 1; i <= 3 ; i++) {
            new Thread(()->{
                try {
                    for (int j = 1; j <= new Random().nextInt(3)+1; j++) {
                        //本店销售总和统计全部
                        su7.saleTotal();
                    }
                }finally {
                    //计数值减一
                    countDownLatch.countDown();
                }
            },String.valueOf(i)).start();
        }
        //主线程等待计数器减为0,再执行后面代码
        countDownLatch.await();

        System.out.println(Thread.currentThread().getName()+"\t"+"销售总额 "+su7.getSaleTotal());
    }
}

输出结果1:main    销售总额 7

输出结果2:main    销售总额 5

输出结果3:main    销售总额 6

2.现在想知道每个销售各自的销售量,使用ThreadLocal

 初始销售量可写为:

    ThreadLocal<Integer> salePersonal = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            //初始值
            return 0;
        }
    };

可以使用Lamda更加简洁的初始化:

ThreadLocal<Integer> salePersonal = ThreadLocal.withInitial(() -> 0);

 完整程序:

//资源类,本门店有三个销售(三个线程)卖小米SU7
class SU7 {
    private int saleTotal;

    public synchronized void saleTotal() {
        saleTotal++;
    }

    使用ThreadL 求单个销售的各自销售量
    //ThreadLocal<Integer> salePersonal = new ThreadLocal<Integer>(){
    //    @Override
    //    protected Integer initialValue() {
    //        //初始值
    //        return 0;
    //    }
    //};
    ThreadLocal<Integer> salePersonal = ThreadLocal.withInitial(() -> 0);

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

    public int getSaleTotal() {
        return saleTotal;
    }

    public void setSaleTotal(int saleTotal) {
        this.saleTotal = saleTotal;
    }
}

//需求一:求三个销售的总体销售额
public class ThreadLocalDemo1 {
    public static void main(String[] args) throws InterruptedException {
        SU7 su7 = new SU7();
        CountDownLatch countDownLatch = new CountDownLatch(3);

        for (int i = 1; i <= 3; i++) {
            new Thread(() -> {
                try {
                    for (int j = 1; j <= new Random().nextInt(3) + 1; j++) {
                        //本店销售总和统计全部
                        su7.saleTotal();
                        //各个销售独立的销售额
                        su7.salePersonal();
                    }
                    System.out.println(Thread.currentThread().getName() + "\t" + "号销售卖出: " + su7.salePersonal.get());
                } finally {
                    //计数值减一
                    countDownLatch.countDown();
                }
            }, String.valueOf(i)).start();
        }
        //主线程等待计数器减为0,再执行后面代码
        countDownLatch.await();

        System.out.println(Thread.currentThread().getName() + "\t" + "销售总额 " + su7.getSaleTotal());
    }
}

输出结果1:

1    号销售卖出: 3
2    号销售卖出: 2
3    号销售卖出: 3
main    销售总额 8

 输出结果2:

1    号销售卖出: 1
3    号销售卖出: 1
2    号销售卖出: 3
main    销售总额 5

3.在线程池复用情况下,如果不清理自定义的ThreadLocal变量,可能会影响后序业务逻辑和造成内存泄漏的问题。

例如:模拟三个线程办理业务,每个顾客未办理状态为0,办理后为1

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

class MyData {
    ThreadLocal<Integer> threadLocalField = ThreadLocal.withInitial(() -> 0);

    public void add() {
        threadLocalField.set(1 + threadLocalField.get());
    }
}

public class ThreadLocalDemo2 {
    public static void main(String[] args) {
        MyData myData = new MyData();
        //模拟一个线程有3个办理业务
        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        try {
            //10个顾客(请求线程),池子里面有3个受理线程,负责处理业务
            for (int i = 1; i <= 10; i++) {
                int finalI = i;
                threadPool.submit(() -> {
                    try {
                        Integer beforeInt = myData.threadLocalField.get();
                        myData.add();
                        Integer afterInt = myData.threadLocalField.get();
                        System.out.println(Thread.currentThread().getName()+"\t"+"工作窗口\t"+"受理第:"+ finalI + "个顾客业务"+
                                "\t beforeInt:"+beforeInt+"\t afterInt:"+afterInt);
                    }finally {
                        myData.threadLocalField.remove();
                    }
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

这里使用ThreadLocal的remove()方法就是确保线程的复用导致结果出错

如果注释掉remove方法则输出结果为:

加上正确结果为:

应用场景

 用户身份信息存储

在做登录鉴权时,一旦鉴权通过之后,就可以吧用户信息存储在ThreadLocal中,这样在后续的所有流程中,需要获取信息的,直接从ThreadLocal中获取。详细案例:http://t.csdnimg.cn/gHWEl

线程安全:ThreadLocal可以用来定义一些需要并发安全处理的成员变量,例如SimpleDateFormat,由于SimpleDateFormat不是线程安全的,可以使用ThreadLocal为每个线程创建一个独立的SimpleDateFormat实例,从而避免线程安全问题。

日志上线文存储:在Log4j等日志框架中,经常使用ThreadLocal来存储与当前线程相关的日志上下文。这允许开发者在日志消息中包含特定与线程的信息,如用户ID或事务ID,对于调试和监控是非常有用。

traceId存储:和上面存储日志上下文类似,在分布式链路追踪中,需要存储本次请求的traceId,通常也都是基于ThreadLocal存储的。

数据库Session:很多ORM框架(对象关系映射),如Hibernate、Mybatis,都是使用ThreadLocal来存储和管理数据库会话的。这样可以确保每个线程都有自己的会话实例,避免了在多线程环境中出现的线程安全问题。

PageHelper分页:PageHelper是MyBatis中提供的分页插件,主要是用来做物理分页的。我们在代码中设置分页参数信息,页码和分页大小等信息都会存储在ThreadLocal中,方便在执行分页时读取数据。

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

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

相关文章

跑腿代购系统开发:重塑便捷生活的新篇章

在快节奏的现代生活中&#xff0c;时间成为了最宝贵的资源之一。随着移动互联网技术的飞速发展&#xff0c;人们对于高效、便捷的生活服务需求日益增长&#xff0c;跑腿代购服务应运而生&#xff0c;并迅速成为连接消费者与日常所需商品及服务的重要桥梁。为了满足这一市场需求…

C++:继承用法详解~

在学完C的类和对象&#xff0c;并掌握了类的核心语法与基本用法之后&#xff1b;我们就得去学习一下继承的语法&#xff0c;与继承的用法。简单概括一下&#xff0c;继承是C中一种代码复用的手段&#xff0c;它允许我们&#xff0c;对已有的类&#xff0c;增添新的成员函数或变…

28 TreeView组件

Tkinter ttk.Treeview 组件使用指南 ttk.Treeview 是 Tkinter 的一个高级控件&#xff0c;用于显示和管理层次化数据。它类似于电子表格或列表视图&#xff0c;但提供了更丰富的功能&#xff0c;如可展开的节点、多列显示等。ttk 模块是 Tkinter 的一个扩展&#xff0c;提供了…

NVM安装及配置

一&#xff1a;下载nvm安装包 https://github.com/coreybutler/nvm-windows/releases 二&#xff1a;安装步骤 三&#xff1a;检查环境变量 &#xff08;1&#xff09;、检查用户变量和系统变量中是否有NVM_HOME和NVM_SYMLINK。一般情况下&#xff0c;安装nvm后&#xff0c;系…

Java分布式架构知识体系及知识体系图

Java分布式架构整体知识体系是一个庞大而复杂的领域&#xff0c;它涵盖了多个方面&#xff0c;旨在帮助开发者构建高性能、高可用、可扩展的分布式系统。以下是对Java分布式架构整体知识体系的概述&#xff1a; 一、分布式理论基础 CAP理论&#xff1a; 一致性&#xff08;Con…

GUI编程04:课堂练习及总结

本节内容视频链接&#xff1a;6、课堂练习讲解及总结_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1DJ411B75F?p6&vd_sourceb5775c3a4ea16a5306db9c7c1c1486b5 根据前三节学习到的Frame、Panel、Button知识&#xff0c;画出一下窗口界面&#xff1a; 实现代码如下…

避坑之:深信服AC跨三层取MAC(核心交换机是锐捷S7808C_RGOS 11.0(4)B2P1)

今天碰到一个奇怪的现象&#xff0c;深信服AC对接锐捷交换机做跨三层取MAC&#xff0c;怎么都获取不到。 一、坑1&#xff1a;交换机不回应snmp报文 1.1 排查锐捷交换机配置 配置上看着没有问题&#xff0c;重新配置了community 1.2 查看snmp报文是否通畅 我的笔记本是win10…

选对文档版本管理软件:10款工具详解

本篇文章中提到的工具包括&#xff1a;1.PingCode&#xff1b;2.Worktile&#xff1b;3.联想Filez&#xff1b;4.蓝凌云&#xff1b;5.阿里云盘&#xff1b;6.360亿方云&#xff1b;7.无忧企业文档&#xff1b;8.DocStar ECM&#xff1b;9.Dropbox Business&#xff1b;10.Shar…

APP 数据抓取 - Charles 抓包工具的使用(Charles 端口配置、CA 证书配置、Charles Android 模拟器配置)

前言说明 此文章是我在学习 Charles APP 抓包时编写&#xff0c;内容都是亲测有效&#xff0c;文章内容也有参考其他人&#xff0c;参考文章如下&#xff1a; Android 手机使用 charles 抓 https 请求&#xff08;保姆级教程&#xff09;网易 mumu 模拟器安装下载 charles 的…

JAVAEE初阶第二节——多线程基础(上)

系列文章目录 JAVAEE初阶第二节——多线程基础(上) 计算机的工作原理 认识线程&#xff08;Thread&#xff09;Thread 类及常见方法线程的状态 文章目录 系列文章目录JAVAEE初阶第二节——多线程基础(上) 计算机的工作原理 一.认识线程&#xff08;Thread&#xff09;1.概念 …

Leetcode面试经典150题-28.找出字符串第一个匹配项的下标

解法都在代码里&#xff0c;不懂就留言或者私信&#xff0c;比第一题稍微难点 用KMP解这个题简直就像大炮打蚂蚁&#xff0c;但是没办法&#xff0c;现在都是这么卷 package dataStructure.bigFactory;public class _28Strstr {public static int strStr(String s1, String s…

EasyCode实现完整CRUD + 分页封装

文章目录 1.创建一个表sys-user2.EasyCode 模板配置1.entity.java.vm2.dao.java.vm3.mapper.xml.vm4.service.java.vm5.serviceImpl.java.vm6.controller.java.vm7.PageInfo.java.vm8.PageResult.java.vm9.SunPageHelper.java.vm 3.EasyCode生成CRUD1.右键表&#xff0c;选择Ge…

Linux系统查看磁盘、内存使用情况、查看当前文件夹内文件详情:free、top、df、du、ls

Liunx系统查看内存使用情况 free&#xff1a;查看当前内存以及交换区内存使用情况&#xff0c;默认显示单位是比特&#xff0c;加上参数-h以易读的方式显示&#xff08;如KB、MB、G&#xff09;&#xff0c;在Linux系统中所有查询加上-h参数均以易读的方式显示。 资源管理器查看…

【C++ Primer Plus习题】7.5

问题: 解答: #include <iostream> using namespace std;int function(int n) {if (n 0)return 1;if (n 1)return 1;return n* function(n - 1); }int main() {int value 0;while (true){cout << "请输入数字:";cin >> value;cout << val…

【读书笔记-《30天自制操作系统》-11】Day12

从本篇内容开始讲解定时器。本篇内容比较简单&#xff0c;首先介绍定时器的概念与设置方法&#xff0c;然后介绍超时的中断处理&#xff0c;并对中断处理函数进行了优化。 1. 定时器 定时器是操作系统中十分重要的功能。它的原理很简单&#xff0c;只是每隔一段时间发送一个…

接口自动化测试框架:SoapUI

SoapUI是一个非常流行的用于Web服务测试的工具。它允许你对SOAP和RESTful Web服务进行测试。在本篇文章中&#xff0c;我们将介绍SoapUI的背景、好处以及企业实际使用该工具的干货。 一、背景 在过去的几年中&#xff0c;Web服务变得越来越流行。由于不同的应用程序可以通过W…

新材料正在加速推动压铸领域3D打印技术应用

3D打印技术&#xff0c;以其独特的逐层累加粉末材料成型方式&#xff0c;正逐步解锁模具制造的无限可能&#xff0c;尤其在实现复杂几何构型与内部结构优化方面展现出非凡潜力。这一技术革新不仅提升了模具制造的精度与效率&#xff0c;还通过随形水路、随形透气钢等创新设计&a…

利润暴涨507%的携程,做对了什么?

关于旅行&#xff0c;1500年前&#xff0c;古罗马思想家圣奥古斯丁曾在《忏悔录》这样评价&#xff1a; “世界是一本书&#xff0c;而不旅行的人只读了其中的一页。” 如今&#xff0c;旅行更是承载了人们逃离工作、抛开忧虑的祈望&#xff0c;成为了理想中的“诗和远方”。…

Google play应用老包突然被暂停和删除了,什么原因?

相信不少开发者都遇到应用突然被暂停和删除的情况&#xff0c;被谷歌判为应用存在欺骗行为&#xff0c;还会在某种程度上“明确”指出应用违规的原因&#xff1a;“您的应用包含可能使用户、用户数据或设备面临风险的代码&#xff0c;以及/或您的应用未能兑现对用户的承诺。” …

治愈系风景视频素材下载网站有哪些?令人治愈美景素材库网站分享

当我们谈到视频制作时&#xff0c;风景视频素材总是能为作品带来生动的视觉效果和震撼的感官体验。无论是用于旅游宣传、自然纪录片&#xff0c;还是日常生活记录&#xff0c;优质的风景视频素材都是不可或缺的。尽管高清美丽的风景素材并不容易获取&#xff0c;特别是那些既免…