菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
342
0

java面试一日一题:讲下ThreadLocal

原创
05/13 14:22
阅读数 1967

问题:请讲下ThreadLocal

分析:首先要了解ThreadLocal的基本原理;其次要理解ThreadLocal发生内存泄漏的原因;最后ThreadLocal是如何做到线程隔离的

回答要点:

主要从以下几点去考虑,

1、ThreadLocal的基本原理

2、ThreadLocal为什么会发生内存泄漏?

3、ThreadLocal如何做到线程隔离?

 

ThreadLocal相当于操作线程中局部变量的一个工具类,其过程是通过操作每个线程内部的ThreadLocalMap来实现的,也就是说在每个线程内部都有一个ThreadLocalMap,该map在存储的时候使用的key为ThreadLocal,value为设置的value,其底层是一个Entry数组。 由于每个线程都有一个ThreadLocalMap,在线程A中使用ThreadLocal放入一个value,那么在线程B中使用ThreadLocal获取的时候,一定是获取不到的,因为每个线程有自己独立的ThreadLocalMap,使用ThreadLocal设置的值最终是存储在线程的ThreadLocalMap中的。

既然是一个Map的结构,就会发生冲突,在HashMap中解决hash冲突使用的是链地址法,在ThreadLocalMap中使用的是线性探测法。

重点看下ThreadLocal的几个重点方法,

get()方法

public T get() {
        //获得当前线程
        Thread t = Thread.currentThread();
        //从当前线程中获得其ThreadLocal.ThreadLocalMap变量
        ThreadLocalMap map = getMap(t);
        if (map != null) {
        //这里的this只得是调用该get()方法的对象,也就是一个ThreadLocal的实例,
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

上面的方法做了注释,在使用ThreadLocal的get方法时首先是获得当前调用线程的一个ThreadLocalMap,然后从该Map中获得value。

set()方法

public void set(T value) {
        //获得当前线程
        Thread t = Thread.currentThread();
        //获得当前线程中的ThreadLocal.ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        if (map != null)
            //使用this(也就是ThreadLocal作为key)放入ThreadLocalMap中
            map.set(this, value);
        else
            createMap(t, value);//新建一个ThreadLocalMap并放入值
    }

从上面可以看到set方法也是要获得当前线程的ThreadLocalMap对象,然后往该对象中放value,下面看下createMap方法

void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

可以看到就是给Thread的threadLocals变量赋值,赋值一个ThreadLocalMap,那么threadLocals肯定是一个ThreadLocal.ThreadLocalMap,看下该变量

通过上面的分析,已经很清晰了ThreadLocal操作的都是当前线程中的变量,和其他线程是没有关系的,所以本身就不存在线程安全的问题;

 

网上经常看到说ThreadLocal有内存泄漏的风险,那么内存泄漏是如何产生的?

上图,画了下get的一个过程,正常情况下是ThreadLocal.get()-->thread->threadLocalMap-Entry[]-entry(key,value)这样一个查找路径,由于Entry中的key是一个weakReference对象,也就是弱引用,在一次gc的过程中会被回收,那么这时Entry中的key就是null了,再通过上面的线路是找不到的,那么这样一个Entry对象中的value就无法再找到了,但由于value又是强引用,不会被回收,该entry对象就造成了内存泄漏,所以正确的使用方法是在使用完之后调用remove()方法。

 

最后有个问题,请广大网友解惑,在ThreadLocal中存储的是引用类型的情况下,是如何做到线程隔离的,望解惑,感谢!

其实如果在ThreadLocal中存储得是引用类型,也就是可变对象,那么在一个线程中对value进行改变,在其他线程中渠道的值肯定是改变后的。所以在使用中基本类型是可以随便用的,当使用到了引用类型要注意;

 

总结:

1、ThreadLocal不是为每个线程创建副本变量,而是由于每个线程中的threadLocals是线程独有的,其他线程无法访问;

2、谨慎的使用引用类型,因为在一个线程中对其值的改变会影响其他线程中值得变化;

3、在使用完,记得remove,防止内存泄漏得风险;

 

发表评论

0/200
342 点赞
0 评论
收藏