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

相关推荐
数据小爬虫@6 分钟前
如何高效利用Python爬虫按关键字搜索苏宁商品
开发语言·爬虫·python
ZJ_.8 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
Narutolxy13 分钟前
深入探讨 Go 中的高级表单验证与翻译:Gin 与 Validator 的实践之道20241223
开发语言·golang·gin
Hello.Reader20 分钟前
全面解析 Golang Gin 框架
开发语言·golang·gin
禁默31 分钟前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Cachel wood38 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Code哈哈笑40 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
gb421528743 分钟前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶44 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
qq_433618441 小时前
shell 编程(二)
开发语言·bash·shell