java面试之ThreadLocal问题

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

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

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

java 复制代码
package com.pxx;

/**
 * Created by Administrator on 2023/9/3.
 */
public class Demo1 {
    private String v1;

    public String getV1() {
        return v1;
    }

    public void setV1(String v1) {
        this.v1 = v1;
    }

    public static void main(String[] args) {
        //开启一个多线程去设置并且访问这个变量

        Demo1 d1 = new Demo1();
        //这里会循环五个线程
        for(int i = 0;i < 5;i++) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //设置并且打印一个变量的数据
                    d1.setV1("data:" + Thread.currentThread().getName());
                    System.out.println("-------------");
                    //取出这个线程对应的名字和值
                    System.out.println(Thread.currentThread().getName() + "=>" + d1.getV1());
                }
            });

            //设置一下每一个线程的名字
            t1.setName("线程" + i);
            t1.start();//开启直接进入到运行状态,然后当前线程开始运行,然后进行下一次循环,下一个线程进来,这个时候进程可能会仙湖干扰
        }

    }
}

下面直接已经线程混乱

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

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

它的目的是保持变量独立

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

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

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

修改一下上面的代码

java 复制代码
package com.pxx;

/**
 * Created by Administrator on 2023/9/3.
 */
public class Demo1 {
    //引入绑定变量的线程对象
    ThreadLocal<String>  tl1= new ThreadLocal();

    private String v1;

    public String getV1() {
       // return v1;
        return tl1.get();//得到通过set绑定的变量
    }

    public void setV1(String v1) {
        //this.v1 = v1;
        tl1.set(v1);//直接把这个v1绑定到对象里面
    }

    public static void main(String[] args) {

        Demo1 d1 = new Demo1();
        //这里会循环五个线程
        for(int i = 0;i < 5;i++) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //设置并且打印一个变量的数据
                    d1.setV1("data:" + Thread.currentThread().getName());
                    System.out.println("-------------");
                    //取出这个线程对应的名字和值
                    System.out.println(Thread.currentThread().getName() + "=>" + d1.getV1());
                }
            });

            //设置一下每一个线程的名字
            t1.setName("线程" + i);
            t1.start();//开启直接进入到运行状态,然后当前线程开始运行,然后进行下一次循环,下一个线程进来,这个时候进程可能会仙湖干扰
        }
    }
}

运行结果:

ThreadLocal与synchronized的区别

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

java 复制代码
package com.pxx;

/**
 * Created by Administrator on 2023/9/3.
 */
public class Demo3 {
    //引入绑定变量的线程对象
    ThreadLocal<String>  tl1 = new ThreadLocal();

    private String v1;

    public String getV1() {
        return v1;
    }

    public void setV1(String v1) {
        this.v1 = v1;
    }

    public static void main(String[] args) {

        Demo3 d1 = new Demo3();
        //这里会循环五个线程
        for(int i = 0;i < 5;i++) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (d1) {//这个加锁了
                        //设置并且打印一个变量的数据
                        d1.setV1("data:" + Thread.currentThread().getName());
                        System.out.println("-------------");
                        //取出这个线程对应的名字和值
                        System.out.println(Thread.currentThread().getName() + "=>" + d1.getV1());
                    }
                }
            });

            //设置一下每一个线程的名字
            t1.setName("线程" + i);
            t1.start();//开启直接进入到运行状态,然后当前线程开始运行,然后进行下一次循环,下一个线程进来,这个时候进程可能会仙湖干扰
        }
    }

}

运行结果:

很明显是锁住了

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

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线程依然在运行

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

相关推荐
tkevinjd21 小时前
力扣146LRU缓存
面试
INFINI Labs21 小时前
使用 Docker Compose 轻松实现 INFINI Console 离线部署与持久化管理
java·docker·eureka·devops·docker compose·console·easyserach
Cosolar21 小时前
国产麒麟系统 aarch64 架构 PostgreSQL 15 源码编译安装完整教程
java·后端
GalaxyPokemon21 小时前
PlayerFeedback 插件开发日志
java·服务器·前端
earthzhang202121 小时前
【2051】【例3.1】偶数
开发语言·数据结构·算法·青少年编程·图论
天天摸鱼的java工程师1 天前
别再写那些重复代码了!8年Java老兵教你用 Hutool 提升开发效率
java·后端
南山安1 天前
面试必考点: 深入理解CSS盒子模型
javascript·面试
喝杯绿茶1 天前
springboot中的事务
java·spring boot·后端
TimelessHaze1 天前
🧱 一文搞懂盒模型box-sizing:从标准盒到怪异盒的本质区别
前端·css·面试
专注VB编程开发20年1 天前
.NET Reflector反编绎,如何移除DLL中的一个公开属性
开发语言·c++·c#