• java面试之ThreadLocal问题


    什么是ThreadLocal,它的基本用法是什么 

    简单来说就是能在多线程中保持变量独立的线程对象

    不用Threadlocal多线程访问同一个变量会出现的问题

    1. package com.pxx;
    2. /**
    3. * Created by Administrator on 2023/9/3.
    4. */
    5. public class Demo1 {
    6. private String v1;
    7. public String getV1() {
    8. return v1;
    9. }
    10. public void setV1(String v1) {
    11. this.v1 = v1;
    12. }
    13. public static void main(String[] args) {
    14. //开启一个多线程去设置并且访问这个变量
    15. Demo1 d1 = new Demo1();
    16. //这里会循环五个线程
    17. for(int i = 0;i < 5;i++) {
    18. Thread t1 = new Thread(new Runnable() {
    19. @Override
    20. public void run() {
    21. //设置并且打印一个变量的数据
    22. d1.setV1("data:" + Thread.currentThread().getName());
    23. System.out.println("-------------");
    24. //取出这个线程对应的名字和值
    25. System.out.println(Thread.currentThread().getName() + "=>" + d1.getV1());
    26. }
    27. });
    28. //设置一下每一个线程的名字
    29. t1.setName("线程" + i);
    30. t1.start();//开启直接进入到运行状态,然后当前线程开始运行,然后进行下一次循环,下一个线程进来,这个时候进程可能会仙湖干扰
    31. }
    32. }
    33. }

    下面直接已经线程混乱 

     一般来说我们可以用锁来解决,比如引入synchronized,这里我们先不用锁,我们用ThreadLocal这个类去解决

    ThreadLocal类去解决线程不同步的问题

    它的目的是保持变量独立

    下面我们去看一下常见的方法

    set():将变量绑定到当前线程中

    get():获取当前线程绑定的变量

    修改一下上面的代码

    1. package com.pxx;
    2. /**
    3. * Created by Administrator on 2023/9/3.
    4. */
    5. public class Demo1 {
    6. //引入绑定变量的线程对象
    7. ThreadLocal tl1= new ThreadLocal();
    8. private String v1;
    9. public String getV1() {
    10. // return v1;
    11. return tl1.get();//得到通过set绑定的变量
    12. }
    13. public void setV1(String v1) {
    14. //this.v1 = v1;
    15. tl1.set(v1);//直接把这个v1绑定到对象里面
    16. }
    17. public static void main(String[] args) {
    18. Demo1 d1 = new Demo1();
    19. //这里会循环五个线程
    20. for(int i = 0;i < 5;i++) {
    21. Thread t1 = new Thread(new Runnable() {
    22. @Override
    23. public void run() {
    24. //设置并且打印一个变量的数据
    25. d1.setV1("data:" + Thread.currentThread().getName());
    26. System.out.println("-------------");
    27. //取出这个线程对应的名字和值
    28. System.out.println(Thread.currentThread().getName() + "=>" + d1.getV1());
    29. }
    30. });
    31. //设置一下每一个线程的名字
    32. t1.setName("线程" + i);
    33. t1.start();//开启直接进入到运行状态,然后当前线程开始运行,然后进行下一次循环,下一个线程进来,这个时候进程可能会仙湖干扰
    34. }
    35. }
    36. }

    运行结果:

     ThreadLocal与synchronized的区别

    先把上面的代码变成synchronized给锁住

    1. package com.pxx;
    2. /**
    3. * Created by Administrator on 2023/9/3.
    4. */
    5. public class Demo3 {
    6. //引入绑定变量的线程对象
    7. ThreadLocal tl1 = new ThreadLocal();
    8. private String v1;
    9. public String getV1() {
    10. return v1;
    11. }
    12. public void setV1(String v1) {
    13. this.v1 = v1;
    14. }
    15. public static void main(String[] args) {
    16. Demo3 d1 = new Demo3();
    17. //这里会循环五个线程
    18. for(int i = 0;i < 5;i++) {
    19. Thread t1 = new Thread(new Runnable() {
    20. @Override
    21. public void run() {
    22. synchronized (d1) {//这个加锁了
    23. //设置并且打印一个变量的数据
    24. d1.setV1("data:" + Thread.currentThread().getName());
    25. System.out.println("-------------");
    26. //取出这个线程对应的名字和值
    27. System.out.println(Thread.currentThread().getName() + "=>" + d1.getV1());
    28. }
    29. }
    30. });
    31. //设置一下每一个线程的名字
    32. t1.setName("线程" + i);
    33. t1.start();//开启直接进入到运行状态,然后当前线程开始运行,然后进行下一次循环,下一个线程进来,这个时候进程可能会仙湖干扰
    34. }
    35. }
    36. }

    运行结果:

    很明显是锁住了

    先来说一下两者的共同点:都是处理多线程并发访问变量的问题

    synchronized:它的效率会低一点,因为相当于就是说,线程是排队进行访问的,就像一个教室只有一个厕所,大家都要进去上,就要排队

    ThreadLocal:效率高,相当于线程可以同时并发访问,效率高,就像一个教室多个厕所,彼此上,但是相互独立

    ThreadLocal的内部结构

    最早的一个设计原理

     JDK8的设计原理

    关注一下JDK8,它是把Thread每一个线程作为了一个主线,然后Entry里面存放的是 ThreadLocal这样一个线程对象

    这样的设计方案有两个好处:

    1.每个map存储的entry变少,因为就是怎么说,Thread线程肯定比ThreadLocal这样一个线程多

    2.那么主线Thread销毁掉,里面的map数据对象也就被销毁了

    我分析一下源码

    先去看set方法

    再去看一下ThreadLocalMap这个类 

     

    set就是添加到了这个map对象里面

    这样不就说明一个问题:解决了线程并发访问,变量出错的问题

    相当是什么,自己去上自己的厕所,彼此之间相互独立不影响

     可能会造成的一个内存泄漏的问题

    他分为两种情况来看:

    第一种: 内存不够了,只有溢出memory overflow

    第二种:内存泄漏memory leak,在堆上的空间得不到释放,会造成浪费,影响了程序的运行速度,这种浪费多了,就会造成内存的溢出 

    我们这里再来引入两个概念

    第一个:什么是强引用?我们正常的引用一个对象,没有指向的时候,就会被gc掉,也就是垃圾回收器给回收掉

    第二个:什么是弱引用?一句来说,遇到gc就会被回收。只要垃圾回收机制一运行,不管jvm的内存空间时候是否足够,都会回收该对象占用的内存

    下面用一张图展示一下引用关系

    假如是key是强引用的情况

    假设ThreadLocal用完了,引用被收回,又因为Map里面的ThreadLocal是一个强引用,所以ThreadLocal对象无法被回收掉

    在没手动remove掉Entry类以及CurrentThread依然运行的情况下,Entry类根本不会被挥挥手,会造成内存泄漏

    假设key是弱引用的情况下,ThreadLocal引用没了,map 里面是一个弱引用指向ThreadLocal,那么就表明ThreadLocal会马上被垃圾回收器给回收掉,他一回收掉,Key就为NULL,那么我们就再也无法访问到value ,value无法被回收,会导致内存泄漏

    很明显源代码给出的是一个弱引用

    上面也说明了强引用还是弱引用都会造成内存泄漏

    那么造成内存泄漏的根本原因就是:

    第一点:Entry类始终存在内存中,没有手动remove

    第二点:CurrentThread线程依然在运行

    好了,祝你早安午安晚安。

  • 相关阅读:
    Elasticsearch
    运维监控系统 PIGOSS BSM 拓扑自动发现原理
    Java实现DFA算法进行敏感词过滤
    xpath用例
    SMBGhost漏洞技术分析与防御方案
    【教3妹学编程-算法题】最长奇偶子数组
    Linux网络命令
    Spring IOC和Spring AOP的实现原理
    美国国家安全实验室员工详细数据在网上泄露
    达利欧《原则》读书思考笔记
  • 原文地址:https://blog.csdn.net/Pxx520Tangtian/article/details/132651957