案例练习理解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中,方便在执行分页时读取数据。

相关推荐
海梨花1 分钟前
【从零开始学习Redis】项目实战-黑马点评D2
java·数据库·redis·后端·缓存
共享家95272 分钟前
linux-高级IO(上)
java·linux·服务器
Sammyyyyy9 分钟前
2025年,Javascript后端应该用 Bun、Node.js 还是 Deno?
开发语言·javascript·node.js
橘子郡1239 分钟前
观察者模式和发布订阅模式对比,Java示例
java
指针满天飞9 分钟前
Collections.synchronizedList是如何将List变为线程安全的
java·数据结构·list
Java技术小馆11 分钟前
重构 Controller 的 7 个黄金法则
java·后端·面试
金銀銅鐵31 分钟前
[Java] 以 IntStream 为例,浅析 Stream 的实现
java·后端
William一直在路上41 分钟前
Python数据类型转换详解:从基础到实践
开发语言·python
看到我,请让我去学习1 小时前
Qt— 布局综合项目(Splitter,Stacked,Dock)
开发语言·qt
GUET_一路向前2 小时前
【C语言防御性编程】if条件常量在前,变量在后
c语言·开发语言·if-else·防御性编程