【Java SE】【多线程】ThreadLocal杂谈

1、ThreadLocal是什么?

面试考点:
ThreadLocal的实现。(当前线程的threadLocalMap)
threadLocalMap的弱键导致的BUG。
面试要求:
(面试官通常会问:”你说说ThreadLocal呢。“,面试官不可能问你飞流直下三千尺的下一句是什么。只会让你:”说说望庐山瀑布。”所以你一定要有一口气全部说完的能力,要整理出自己的一套思维架构)

1、概念:每一个线程都维护了一个自己的map,ThreadLocal是一个工具类,它是用于辅助线程使用这个map存储数据的工具。
2、常用方法:ThreadLocal常用的方法有两个,get和set方法,set方法的底层是使用的当前线程的ThreadLocalMap来存储这个value,键是实例化的threadLocal。
3、实现细节:这个键它是使用的弱引用定义的,是因为线程存在,则map一直存在,为了避免内存溢出需要控制map的内存占用,此时为了避免频繁主动调用remove方法。就使用弱引用,在自动gc的时候清楚弱引用,然后当线程主动调用remove方法或set方法后,会去清除全部没有键的值。value不设置为弱引用,是为了防止,键在值不在,这样会加一些判断逻辑,比较丑陋。
4、应用场景是:线程调用公共的方法,用普通的传参方式会比较复杂,也不好定义方法,所以把参数存到线程自己的map中,这样降低了方法和线程的耦合性。

1)ThreadLocal的作用

ThreadLocal的作用是在线程内部维护一个变量(一个ThreadLocal实例,在一个线程中有且仅有一个变量),其他线程不可访问。

2)为什么在一个线程中只有一个变量呢?是因为:

在一个线程中,调用ThreadLocal实例的 get(this) 方法,会调用当前线程本身的 ThreadLocalMap 实例的 getEntity(ThreadLocal<?>) 方法,ThreadLocalMap 中的这个方法的参数就是ThreadLocal它本身!getEntity(ThreadLocal<?>) 为什么要传一个ThreadLocal进去才能获取Entry的值?是因为Entity这个键值类的键就是 ThreadLocal<?>

3)是不是绕晕了?

也是,ThreadLocal牛逼、难理解也就在于:它的内部类的内部类,把它自己当成键!!

在这里插入图片描述

4)为什么ThreadLocal是线程独立的?
//调用set方法设置值
public void set(T value) {
    Thread t = Thread.currentThread();//得到当前线程
    ThreadLocalMap map = getMap(t);//得到当前线程的threadLocals
    if (map != null)
        map.set(this, value);//当前线程的threadLocals存入数据,键为这个ThreadLocal本身,值为传进来的value
    else
        createMap(t, value);//如果当前线程没有threadLocalMap,则初始化,并存储。
}

看上面的代码可知,ThreadLocalset操作存储数据,其实是存储在当前线程的threadLocalMap中,所以每个线程的ThreadLocal存储的数据,互不影响。

看吧,getMap操作。
在这里插入图片描述

一起来使用它吧!

2、ThreadLocal使用

ThreadLocal是一个泛型类。

1)代码
package com.tangxz.hashmap;

import java.util.Random;

/**
 * @author: 唐小尊
 * @email: 1171702529@qq.com
 * @cate: 2021/05/10 18:44
 */
public class ThreadLocalTest {
    private static ThreadLocal<Integer> intThreadLocal= new ThreadLocal<>();
    public static void main(String[] args) {
        new Thread(() -> {
            Integer num=new Random().nextInt();
            intThreadLocal.set(num);
            System.out.println(Thread.currentThread().getName()+" put num:"+num);
            //A 、B对象通过 currentThread 来获取值
            new A().get();
            new B().get();
        },"》》》》线程一").start();
        new Thread(() -> {
            Integer num=new Random().nextInt();
            intThreadLocal.set(num);
            System.out.println(Thread.currentThread().getName()+" put num:"+num);
            //A 、B对象通过 currentThread 来获取值
            new A().get();
            new B().get();
        },"线程二").start();
    }
    //A对象  输出 当前线程中 num 的数据
    static class A{
        public void get(){
            System.out.println(Thread.currentThread().getName()+" has a num---A:"+intThreadLocal.get());
        }
    }
    //B对象 输出 当前线程中 num 的数据
    static class B{
        public void get(){
            System.out.println(Thread.currentThread().getName()+" has a num---B:"+intThreadLocal.get());
        }
    }
}

2)结果:
① 通过ThreadLocal 可以使一个线程内部有一个全局变量。

可以很明显的发现线程一和线程二的值不同。

类A、类B在同一个线程中,均打印出了相同的 intThreadLocal.get() 结果。

所以确定,在同一线程中,绝对 ThreadLocal 可共用。

② 唯一性和线程间不可互相干预,都可以自己去测试。

在这里插入图片描述

3、ThreadLocal面试题

1)ThreadLocal 是什么?

ThreadLocal 是一个本地线程副本变量工具类。主要用于将私有线程和该线程存放的副本对象做一个映射,各个线程之间的变量互不干扰,在高并发场景下,可以实现无状态的调用,适用于各个线程不共享变量值的操作。

2)ThreadLocal 工作原理是什么?

ThreadLocal 原理:每个线程的内部都维护了一个 ThreadLocalMap,它是一个 Map(key,value)数据格式,key 是一个弱引用,也就是 ThreadLocal 本身,而 value 存的是线程变量的值。

也就是说 ThreadLocal 本身并不存储线程的变量值,它只是一个工具,用来维护线程内部的 Map,帮助存和取变量。

3)ThreadLocal 如何解决 Hash 冲突?

开放定址法

4)ThreadLocal 的内存泄露是怎么回事?

1、key、value的值是强引用。即使key已经被gc了,但它还是强引用,不会被回收。除非触发线程中的ThreadLocalMap,中的set方法和remove方法,会清理一下该线程中的内存空间,把键已经被回收了的键值对的值给置为null。实现解除关系。

2、垃圾回收的垃圾判定方法是可达性分析算法。

ThreadLocal 在 ThreadLocalMap 中是以一个弱引用身份被 Entry 中的 Key 引用的,因此如果 ThreadLocal 没有外部强引用来引用它,那么 ThreadLocal 会在下次 JVM 垃圾收集时被回收。这个时候 Entry 中的 key 已经被回收,但是 value 又是一强引用不会被垃圾收集器回收,这样 ThreadLocal 的线程如果一直持续运行,value 就一直得不到回收,这样就会发生内存泄露。

5)为什么 ThreadLocalMap 的 key 是弱引用?

我们知道 ThreadLocalMap 中的 key 是弱引用,而 value 是强引用才会导致内存泄露的问题,至于为什么要这样设计,这样分为两种情况来讨论:

  • key 使用强引用:这样会导致一个问题,引用的 ThreadLocal 的对象被回收了,但是 ThreadLocalMap 还持有 ThreadLocal 的强引用,如果没有手动删除,ThreadLocal 不会被回收,则会导致内存泄漏。
  • key 使用弱引用:这样的话,引用的 ThreadLocal 的对象被回收了,由于 ThreadLocalMap 持有 ThreadLocal 的弱引用,即使没有手动删除,ThreadLocal 也会被回收。value 在下一次 ThreadLocalMap 调用 set、get、remove 的时候会被清除。

比较以上两种情况,我们可以发现:由于 ThreadLocalMap 的生命周期跟 Thread 一样长,如果都没有手动删除对应 key,都会导致内存泄漏,但是使用弱引用可以多一层保障,弱引用 ThreadLocal 不会内存泄漏,对应的 value 在下一次 ThreadLocalMap 调用 set、get、remove 的时候被清除,算是最优的解决方案。

6.ThreadLocal 的应用场景有哪些?

ThreadLocal 适用于独立变量副本的情况,比如 Hibernate 的 session 获取场景。

4、ThreadLocal源码

ThreadLocal就像一个工具类,把自己的内部类ThreadLocalMap提供给别人。

主要方法有get和set方法

ThreadLocalMap内部存储数据,用的是一个数组。使用开放定址法存储。

所以如果删除某个元素,会产生一个空位,导致这个数组乱掉。

为什么要用弱引用?不用强引用。

1、如果是强引用,则每次删除一个键值对,需要手动的去像map一样,删除某一个,这样比较麻烦。

2、实战中,需要调用下面这个方法,主动释放弱引用和强引用。在这里我对弱引用的想法时,它只是个标志,虽然是弱引用,但是它还是在GC的时候被移除内存,所以如果存在很多很多的kv,键值对,在内存达到某一值的时候,再自动gc,则虽然弱引用删了,但是强引用还在。所以我们不可能依靠这个弱引用来实现回收内存。只能规范自己,在使用完成该数据后,将该数据,给remove移出内存。

threadLocal.remove();
WeakHashMap的所有方法,都会调用清除强引用Value的方法。(都会调用getTable方法得到表,getTable会调用清除方法)
如果用set的里面的算法来进行清除,如果插入数据太快,来不及回收,就会报异常
  • 弱引用的出现是为了垃圾回收的
  • 一个对象只有弱引用指向它的时候,它是可以被回收的
  • 弱引用是在GC 发生的时候就进行回收,不管当时内存是否充足
  • 如果你在创建弱引用指定一个引用队列的话,弱引用对象被回收的时候,会把该对象放入引用队列中
  • 为了安全使用,每次都要判断下是否为空来判断该对象是否已经被回收,来避免空指针异常

https://zhuanlan.zhihu.com/p/140433592

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值