【多线程】ThreadLocal

目录

一、ThreadLocal 概述

1.1 ThreadLocal 是什么

1.2、ThreadLocal 的作用

二、ThreadLocal 的使用和源码分析

2.1 ThreadLocal 如何使用

2.1.1 常见方法

2.1.2 使用示例

2.2 ThreadLocal 源码分析

2.2.1 set() 方法分析,以及存储结构

2.2.2 ThreadLocalMap 分析

2.3 ThreadLocal 的内存泄漏

2.3.1 强引用和弱引用

2.3.2 ThreadLocal 机制中可能存在的内存泄漏情况

2.3.3 若使用线程池时,没有清理 ThreadLocal


一、ThreadLocal 概述

1.1 ThreadLocal 是什么

ThreadLocal 会为每一个线程中都创建一个副本,每个线程仅可访问自己内部的副本变量,无法互相访问其他线程的副本变量。

本质上是每个线程都有一个 ThreadLocalMap 类型的变量 threadLocals,以设置的 ThreadLocal 对象为 Key,要保存的值为 Value,存储到这个 threadLocals 中。

由于每个线程都有自己的 threadLocals,故实现了线程隔离。且 map 中可以记录多个值,可以通过 new 新的 ThreadLocal 对象实现多个值保存。

 

1.2、ThreadLocal 的作用

ThreadLocal 一般用在线程安全问题中,通过为每一个线程创建变量副本来解决共享变量并发访问的冲突问题。如线程池,或是会话管理。

 

二、ThreadLocal 的使用和源码分析

2.1 ThreadLocal 如何使用

2.1.1 常见方法

set()  保存值到 ThreadLocal 中

get()  从 ThreadLocal 中取值

 remove()  从 ThreadLocal 中取值

 

2.1.2 使用示例

public class TestThLocal {

    static ThreadLocal<Person> threadLocal = new ThreadLocal<>();
    static ThreadLocal<Person> threadLocal2 = new ThreadLocal<>();

    public static void main(String[] args) {
        new Thread(() -> {
            threadLocal2.set(new Person());
            threadLocal.set(new Person());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("删除前获取: " + threadLocal.get());
            System.out.println("删除前获取2: " + threadLocal2.get());
            threadLocal.remove();
            threadLocal2.remove();
            System.out.println("删除后获取: " + threadLocal.get());
            System.out.println("删除后获取2: " + threadLocal2.get());
        }).start();
    }

    static class Person {
        String name = "xiaozhang";
    }
}

 

2.2 ThreadLocal 源码分析

2.2.1 set() 方法分析,以及存储结构

先查看 set() 方法的源码

public void set(T value) {
    // 获取到当前线程
    Thread t = Thread.currentThread();
    // 从当前线程中取出这个线程的变量 threadLocals
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // this 是调用 set() 方法的 ThreadLocal 对象
        // value 是要保存的值
        map.set(this, value);
    else
        // map 不存在,则新建一个 map
        createMap(t, value);
}

那么,看一下 getMap() 的作用,可以看到是获取当前这个线程的变量 threadLocals,这个变量的类型是 ThreadLocalMap

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

  

由此我们可以总结:

ThreadLocal 的原理就是在每一个线程中都有一个 ThreadLocalMap 类型的变量 threadLocals,用于存储ThreadLocal 设置的值。存储方式就是,把 ThreadLocal 对象作为 Key 值,要存储的内容作为 Value,存储有两个特点:支持多个 ThreadLocal 存储,相同的 ThreadLocal 会被覆盖

结构图如下:

  

由于每个线程都是维护自己的 threadLocals 变量,故可以实现线程隔离

 

2.2.2 ThreadLocalMap 分析

可以看到存储值是设置 ThreadLocalMap 的键值对来实现的,

进而查看 Entry 的结构,发现 Entry 是一个弱引用。这就引出了 ThreadLocal 的内存泄漏问题

 

2.3 ThreadLocal 的内存泄漏

从 2.2.2 中我们了解到,用来存储键值对的 Entry 是一个弱引用对象。

2.3.1 强引用和弱引用

● 强引用

对象只要有引用,就不会被回收

● 弱引用

当没有强引用来引用这个对象,则GC回收器到来时,就会回收掉这个弱引用对象

 

2.3.2 ThreadLocal 机制中可能存在的内存泄漏情况

● 线程关闭了,但 ThreadLocal 没有被回收造成内存泄漏

在 Thread 源码中,关闭线程会直接将 threadlocals 设为 null,

这是若 ThreadLocal 是强引用,则只要线程不退出,内部存储的 ThreadLocal 也不会被回收,就变成了内存泄漏。

故 jdk 中使用了 弱引用,让线程没有对 ThreadLocal 的引用时,ThreadLocal 对象会被 GC 回收。

 

● ThreadLocal 被回收,但是 value 没回收造成内存泄漏

从上面可知,当 ThreadLocal被销毁后,ThreadLocal 会被 GC 回收。但若Value中存的也是一个引用类型且是强引用,则 Value 值指向的内存空间不会随着 ThreadLocal 回收而回收,这就是 ThreadLocal 造成内存泄漏的原因

防止方式:

当 ThreadLocal 使用完毕后,一定要记得调用 remove() 方法!

 

2.3.3 若使用线程池时,没有清理 ThreadLocal

当使用的是线程池,核心线程不会被退出,若没有清理 ThreadLocal,则会导致下次使用,会访问到上次使用时存下来的值。且若是重复使用,可能造成 ThreadLocalMap 存储越来越多的无用内容,造成内存占用

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值