ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

java面试系列(3)—— ThreadLocal

2022-01-31 00:02:35  阅读:198  来源: 互联网

标签:java Thread ThreadLocalMap 面试 ThreadLocal 线程 Entry public


说一下ThreadLocal

1.ThreadLocal 是java中所提供的线程本地存储机制,可以利用该机制将数据(如对象)缓存在某个线程内部,该线程可以在任意时刻、任意方法中获取缓存的数据
2.ThreadLocal底层是通过ThreadLocalMap实现的,每个Thread对象(注意不是ThreadLocal对象)中都存在一个ThreadLocalMap,Map的key为ThreadLocal对象,Map的value为需要缓存的值
3.如果线程池中使用ThreadLocal会造成内存泄漏,因为当使用完ThreadLocal对象后,理应当把设置的key、value,也就是Entry对象进行回收,但是线程池中的线程不会回收,而线程对象是通过强引用指向ThreadLocalMap,ThreadLocalMap也是通过强引用指向Entry对象,线程不被回收,Entry对象也就不会被回收,从而出现内存泄漏。解决方法,在一个线程使用完ThreadLocal后,手动调用ThreadLocal对象的remove方法移除Entry对象。
4.ThreadLocal经典应用场景是连接管理(一个线程持有一个连接,该连接对象可以在不同的方法之间进行传递,线程之间不共享一个连接)

一.ThreadLocal使用案例

//定义一个Person类,其中的成原变量使用了ThreadLocal
public class Person {
    ThreadLocal<String> name=new ThreadLocal<>();

    public String getName() {
        return name.get();
    }

    public void setName(String name) {
        this.name.set(name);;
    }

    public Person() {
    }
}

//测试方法,创建了两个线程,每个线程分别等待3秒,并对Person中的成员变量进行赋值
public class Test {
    public static void main(String[] args) {
        Person person = new Person();
        new Thread(()->{
            try{
                Thread.sleep(3000);
            }catch (Exception e){
                System.out.println(e);
            }
            person.setName("一号");
            System.out.println("1线程:"+person.getName());
        }).start();


        new Thread(()->{
            try{
                Thread.sleep(3000);
            }catch (Exception e){
                System.out.println(e);
            }
            person.setName("二号");
            System.out.println("2线程:"+person.getName());
        }).start();
    }
}

100次测试结果都为:

1线程:一号
2线程:二号

主要原因是就是ThreadLocal:ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,防止自己的变量被其它线程篡改。
底层其实就是当我们在一个公共类Person中声明了一个ThreadLocal对象后,当一个线程使用该person并使用set进行赋值时,会将 <ThreadLocal,对应的数据> 封装成一个Entry对象,并将其放入每个线程独有的ThreadThreadMap中。
我们看看在Thread类中这个Map是哪个成员变量:
在这里插入图片描述
结构如下:
在这里插入图片描述

当一个线程创建时,就会初始化这个threadLocals,当我们调用set方法时,底层代码如下;

//class ThreadLocal
 public void set(T value) {
        Thread t = Thread.currentThread();   //当前线程
        ThreadLocalMap map = getMap(t);   //找到当前线程对应的threadLocals
        if (map != null)
            map.set(this, value); // map在线程开始时就已经创建好了,我们这里直接将ThreadLocal和value装入map中
        else
            createMap(t, value);
    }

调用get方法时,源码如下:

public T get() {
       Thread t = Thread.currentThread();
       ThreadLocalMap map = getMap(t);
       if (map != null) {
           ThreadLocalMap.Entry e = map.getEntry(this);
           if (e != null) {
               @SuppressWarnings("unchecked")
               T result = (T)e.value;
               return result;
           }
       }
       return setInitialValue();
   }

从上面可知,每个线程执行的get和set都是执行线程本地缓存的数据,从而使线程之间非共享数据得到了隔离。

二.内存泄漏问题

内存泄漏:不再使用的对象没有被垃圾回收,导致jvm的内存压力逐渐增大

当一个线程结束后,线程相关的对象如ThreadLocalMap中的Entry会被垃圾回收。但是在线程池中,一个线程不会被销毁,他会做不同的逻辑任务,Entry对象不会被垃圾回收,但是之前的Entry对象我们不会再使用,这样就耗费了jvm的内存,即内存泄漏问题。解决方法,在公共对象中提供remove方法

public void remove(){
        this.name.remove();
    }

这样在一个线程使用完一个ThreadLocal属性后,手动调用remove方法,将本线程中ThreadLocalMap中对应ThreadLocal的key进行删除即可,这样就避免了内存泄漏问题。

标签:java,Thread,ThreadLocalMap,面试,ThreadLocal,线程,Entry,public
来源: https://blog.csdn.net/qq_56769991/article/details/122756600

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有