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

目录

案例练习

应用场景


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

案例练习

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

java 复制代码
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

初始销售量可写为:

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

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

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

完整程序:

java 复制代码
//资源类,本门店有三个销售(三个线程)卖小米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

java 复制代码
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中,方便在执行分页时读取数据。

相关推荐
m0_587383003 小时前
广州 24 小时自助健身房解决方案技术实现与实战指南
java·spring·小程序·架构·需求分析
·云扬·3 小时前
Prompt 工程与安全:从 System 设计到注入防御
java·人工智能·安全·ai·prompt
专注仿真3 小时前
引擎扩展性与集成设计深度剖析:从 14 个扩展点到 DIS→HLA 演进之路
开发语言·算法·仿真引擎·推演引擎
Best-Wishes3 小时前
科来网络分析系统(技术交流版)使用说明书
开发语言·junit·php
代码不停4 小时前
自动化测试(java)
java·开发语言·selenium·自动化
鬼手点金6 小时前
opencode-rules 插件
java·开发语言·前端·人工智能·chrome·git·github
爱上网的小舟6 小时前
星际战士2启动错误怎么办?5种游戏报错解决方法
开发语言·php
paopaokaka_luck6 小时前
滑雪场管理系统(AI智能助手流式对话,ECharts 运营数据分析、教练、雪具与场地一体化预约,钱包支付退款、订单核销与服务评价)
java·人工智能·spring boot·网络协议·数据分析·echarts
阿里嘎多学长7 小时前
2026-10-12 GitHub 热点项目精选
开发语言·程序员·github·代码托管
Wang's Blog7 小时前
Java 项目实战: 交友项目 - Dubbo 的内部结构与一次调用的完整链路
java·dubbo