• JUC P8 ThreadLocal 基础+代码


    JUC P8 ThreadLocal 基础+代码

    教程:https://www.bilibili.com/video/BV1ar4y1x727?p=100

    引出问题

    ThreadLocal 和 TreadLocalMap 数据结构关系?
    ThreadLocal 中的 key 是弱引用,为什么?
    ThreadLocal 内存泄漏问题是什么?
    ThreadLocal 中最后为什么要加 remove 方法?

    1. ThreadLocal 描述

    ThreadLocal 提供线程局部变量。这些变量与正常的变量不同,因为每个线程在访问 ThreadLocal 实例的时候(通过 get 或 set 方法)都有自己的、独立初始化的变量副本。ThreadLocal 实例通常是类中的私有静态字段,使用它的目的是希望将状态(例如,用户 ID 或事务 ID)与线程关联起来。

    2. 应用

    2.1 五个销售卖房子,集团高层只关心销售总量的准确统计数

    实例方法上锁

    @Slf4j(topic = "c.Test")
    public class Test {
        public static void main(String[] args) {
            House house = new House();
            for (int i = 0; i < 5; i++) {
                new Thread(() -> {
                    int size = new Random().nextInt(10) + 1;
                    log.info("{} 卖出了 {} 套房子", Thread.currentThread().getName(), size);
                    for (int j = 0; j < size; j++) {
                        house.saleHouse();
                    }
                }, "t" + i).start();
            }
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.info("{} 共卖出了 {} 套房子", Thread.currentThread().getName(), house.saleCount);
        }
    }
    
    class House {
        public int saleCount;
        public synchronized void saleHouse() {
            saleCount++;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在这里插入图片描述

    2.2 五个销售都有自己的销售额指标,自己专属自己的,不和别人掺和

    @Slf4j(topic = "c.Test")
    public class Test {
        public static void main(String[] args) {
            House house = new House();
            for (int i = 0; i < 5; i++) {
                new Thread(() -> {
                    int size = new Random().nextInt(10) + 1;
                    for (int j = 0; j < size; j++) {
                        house.saleVolumeByThreadLocal();
                    }
                    log.info("{} 卖出了 {} 套房子", Thread.currentThread().getName(), house.saleVolume.get());
                }, "t" + i).start();
            }
        }
    }
    
    class House {
        ThreadLocal<Integer> saleVolume = ThreadLocal.withInitial(() -> 0);
        public void saleVolumeByThreadLocal() {
            saleVolume.set(saleVolume.get() + 1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    在这里插入图片描述

    这样写的隐患:
    线程池的场景下因为线程会复用,如果不清理自定义的 ThreadLocal 变量,可能会影响后续业务逻辑和造成内存泄漏等问题。尽量在代理中使用 try-finally 块进行回收。

    try {
        int size = new Random().nextInt(10) + 1;
        for (int j = 0; j < size; j++) {
            house.saleVolumeByThreadLocal();
        }
        log.info("{} 卖出了 {} 套房子", Thread.currentThread().getName(), house.saleVolume.get());
    } finally {
        house.saleVolume.remove();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.3 线程池场景

    不清理 ThreadLocal 变量
    @Slf4j(topic = "c.Test")
    public class Test {
        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(() -> {
                        Integer before = myData.threadLocalField.get();
                        myData.add();
                        Integer after = myData.threadLocalField.get();
                        log.info("before: {} -> after: {}", before, after);
                    });
                }
            } finally {
                threadPool.shutdown();
            }
        }
    }
    
    class MyData {
        public ThreadLocal<Integer> threadLocalField = ThreadLocal.withInitial(() -> 0);
    
        public void add() {
            threadLocalField.set(threadLocalField.get() + 1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    在这里插入图片描述
    每个线程每执行完自己的任务后就应该恢复到原始状态,否则会影响到后续的线程。

    清理 ThreadLocal 变量
    @Slf4j(topic = "c.Test")
    public class Test {
        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 before = myData.threadLocalField.get();
                            myData.add();
                            Integer after = myData.threadLocalField.get();
                            log.info("before: {} -> after: {}", before, after);
                        } finally {
                            // 清除线程变量
                            myData.threadLocalField.remove();
                        }
                    });
                }
            } finally {
                threadPool.shutdown();
            }
        }
    }
    
    class MyData {
        public ThreadLocal<Integer> threadLocalField = ThreadLocal.withInitial(() -> 0);
    
        public void add() {
            threadLocalField.set(threadLocalField.get() + 1);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    在这里插入图片描述

    3. 面试题

    3.1 Thread,ThreadLocal,ThreadLocalMap 关系

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    ThreadLocalMap 从字面上可以看出这是一个保存 ThreadLocal 对象的 map,以 ThreadLocal 为 key。

    JVM 内部维护了一个线程版的 Map,(通过 ThreadLocal 对象的 set 方法,结果把 ThreadLocal 对象自己当作 key,放进了 ThreadLocalMap 中),每个线程要用到这个线程的时候,用当前的线程去 Map 中获取,通过这样让每个线程都有了自己独立的变量,人手一份,竞争条件被彻底消除,在并发模式下是绝对安全的变量。

    3.2 ThreadLocal 内存泄漏问题

    内存泄漏:占着茅坑不拉屎,不再使用的对象占用的内存不能被回收。

    为什么要用弱引用?

    To help deal with very large and long-lived usages, the hash table entries use WeakReferences for keys. However, since reference queues are not used, stale entries are guaranteed to be removed only when the table starts running out of space.

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    4. 最佳实践

    1. 初始化方式:
    ThreadLocal<Integer> threadLocalField = ThreadLocal.withInitial(() -> 0);
    
    • 1
    1. 建议把 ThreadLocal 修饰为 static
      在这里插入图片描述
      在这里插入图片描述

    2. 线程使用 ThreadLocal 完之后必须进行 remove 回收

    3. 使用条件
      在这里插入图片描述

  • 相关阅读:
    快速了解并掌握CLM 陆面过程模式
    JAVA的@EXCEL导出导入常用注解汇总
    【EI会议】第三届信息控制、电气工程及轨道交通国际学术会议(ICEERT 2023)
    【无标题】H5页面解决点击页面处关闭键盘,触发了页面的事件的问题
    python 小案例83
    庖丁解牛:NIO核心概念与机制详解 03 _ 缓冲区分配、包装和分片
    redis运维(十六) 有序集合
    2. 线性表的基本概念 + 基本操作
    46从零开始用Rust编写nginx,数据还能这么传,多层代理(IP多级代理)搭建
    回溯算法——好的开始
  • 原文地址:https://blog.csdn.net/qq_39906884/article/details/127710064