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

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

相关推荐
吴声子夜歌3 小时前
ApacheCommons——commons-pool2(高性能通用对象池框架)
java·开发语言·apache
kruptos8 小时前
分布式系统怎么“选主“?Raft 共识一次讲清
开发语言·分布式·php·共识算法
xcl09258 小时前
幼儿托育系统开发实战:从需求分析到上线全流程指南
java·大数据·需求分析
吴声子夜歌8 小时前
ApacheCommons——commons-cli(命令行参数解析)
java·开发语言·apache
小小猪的春天9 小时前
Java 手写第一个 MCP Server:Spring AI MCP 半小时跑通
java·人工智能·spring boot·ai编程
find1star9 小时前
LeetCode 141:环形链表
java·算法·leetcode·链表
今天AI了吗9 小时前
DeepSeek Harness 深度解析:从评测架构到实战落地
java·网络·数据库·人工智能·架构·java-ee
benchmark_cc9 小时前
REST API 和 Python SDK 应该怎么选?量化交易数据接口选型实战
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
王的宝库10 小时前
Go 项目结构:从单文件到标准工程布局
开发语言·后端·golang
Tairitsu_H10 小时前
[C++] 深入理解红黑树:封装set与map
开发语言·c++·set·map·红黑树·模拟实现