(四)ThreadLocal

参考原文

一、简介

首先ThreadLocal 是一个线程的局部变量(其实就是一个Map),ThreadLocal会为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,将对象的可见范围限制在同一个线程内,而不会影响其它线程所对应的副本。
这样做其实就是以空间换时间的方式(与synchronized相反),以耗费内存为代价,但大大减少了线程同步(如synchronized)所带来性能消耗以及减少了线程并发控制的复杂度。

public class Demo2 {

    static String str;

    public void setStr(String str) {
        this.str = str;
    }

    public static String getStr() {
        return str;
    }

    public static void main(String[] args) {

        Demo2 demo2 = new Demo2();

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                demo2.setStr(Thread.currentThread().getName()+"的数据");
                System.out.println("----------------");
                System.out.println(Thread.currentThread().getName()+"获取-----"+demo2.getStr());
            },"线程"+i).start();
        }
    }
}

需求:线程隔离,各自线程输出各自的数据

多运行几遍,可能会得到下面的结果:
在这里插入图片描述
解决方法:使用ThreadLocal。

共享变量一直是并发中的老大难问题,每个线程都对它有操作权,所以线程之间的同步很关键,锁也就应运而生。这里换一个思路,是否可以把共享变量私有化?即每个线程都拥有一份共享变量的本地副本,每个线程对应一个副本,同时对共享变量的操作也改为对属于自己的副本的操作,这样每个线程处理自己的本地变量,形成数据隔离。事实上这就是ThreadLocal了。

在这里插入图片描述

public class Demo2 {

    private ThreadLocal<String> t = new ThreadLocal<>();

    public void setStr(String str) {
        t.set(str);
    }

    public String getStr() {
        return t.get();
    }

    public static void main(String[] args) {

        Demo2 demo2 = new Demo2();

        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                demo2.setStr(Thread.currentThread().getName()+"的数据");
                System.out.println("----------------");
                System.out.println(Thread.currentThread().getName()+"获取-----"+demo2.getStr());
            },"线程"+i).start();
        }
    }
}

在这里插入图片描述

二、ThreadLocal内部结构

在JDK7中
在这里插入图片描述
每个ThreadLocal都创建一个Map,然后用线程作为Map的key,要存储的局部变量作为Map的value,这样能达到各个线程之间的局部变量相互隔离。

在JDK8以后
每个Thread线程内部都有一个ThreadLocalMap,这个Map的key就是ThreadLocal实例本身,value才是真正存储的值

具体过程:

  1. 每个Thread线程内部都有一个Map(ThreadLocalMap)
  2. Map里面存储ThreadLocal对象(key)和线程的变量副本(value)
  3. Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责从Map获取和设置线程变量值
  4. 对于不同的线程,每次获得副本时,别的线程并不能获得当前线程副本值,形成了副本的隔离,互不干扰
    在这里插入图片描述
    JDK8的设计方案两个好处

1.每个Map存储的Entry数量变少。因为之前的存储数量由Thread的数量决定,现在是由ThreadLocal的数量决定。在实际开发中,ThreadLocal的数量往往少于线程的数量。数量变少也可以尽量避免哈希冲突。
2.当Thread销毁的时候,ThreadLocalMap也会随之销毁,减少内存的使用。

三、ThreadLocal 内存泄漏

ThreadLocal 在没有外部强引用时,发生 GC 时会被回收,那么 ThreadLocalMap 中保存的 key 值就变成了 null,而 Entry 又被 threadLocalMap 对象引用,threadLocalMap 对象又被 Thread 对象所引用,那么当 Thread 一直不终结的话,value 对象就会一直存在于内存中,也就导致了内存泄漏,直至 Thread 被销毁后,才会被回收。
简单说:就是ThreadLocalMap中的key是弱引用,发生GC时会被回收,而value一直在啊内存中没有被回收,造成内存泄漏
在这里插入图片描述
即使key使用弱引用,也不能完全保证不会内存泄漏
在这里插入图片描述
在这里插入图片描述

那么如何避免内存泄漏呢?

在使用完 ThreadLocal 变量后,需要我们手动 remove 掉,防止 ThreadLocalMap 中 Entry 一直保持对 value 的强引用,导致 value 不能被回收,其中 remove 源码如下所示:
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值