转载

当面试官问到 ThreadLocal 时,我们应具备怎样的谈资?

当面试官问到 ThreadLocal 时,我们应具备怎样的谈资?

按照一问一答的方式来写吧。

面试官:你能 说一下java中有哪些机制能保证线程安全吗

据我了解的,java中保证线程安全的机制有 ThreadLocal 。首先说下锁...(被面试官强行打断( 不要问我为什么要强行打断 ))

面试官:你先讲讲什么是ThreadLocal

ThreadLocal是java中的一个泛型类,该类提供了 线程局部能量 。也就是说它为每个线程都创建了一个变量副本, 线程操作自己的变量副本不会影响到其他线程

ThreadLocal 使用起来不难,看下面代码。

当面试官问到 ThreadLocal 时,我们应具备怎样的谈资?

示例代码中开启了两个线程:主线程和一个子线程。只定义了一个ThreadLocal变量,两条线程中分别调用ThreadLocal的set()进行赋值,最后打印结果。来看下输出。

当面试官问到 ThreadLocal 时,我们应具备怎样的谈资?

显然,两条线程都调用set()时,不会相互影响,正如刚才所说的: ThreadLocal为每个线程都创建了一个变量副本,线程操作自己的变量副本不会影响到其它线程。

面试官:那你能解释一下ThreadLocal的工作原理吗

(涉及到底层的就有点难度了,不过问题不大,我们先假装思考几秒......)

在说原理之前,我先说下ThreadLocalMap。在ThreadLocal源码中, ThreadLocalMap 这个词频繁出现在关键代码处,查看源码发现, ThreadLocalMapThreadLocal 的一个 静态内部类 ,从它的名字可以看出,它应该是一个集合,类似HashMap。

但是 ThreadLocalMap 并没有实现Map接口,它实际是初始化了一个大小为16的 Entry类型数组 ,每个数组单元存储一个 键值对 (key-value),键(key)的类型为 ThreadLocal ,这些可以通过源码看到。

当面试官问到 ThreadLocal 时,我们应具备怎样的谈资?

容量大小为16,table类型为Entry。(Entry是ThreadLocalMap的 内部类

当面试官问到 ThreadLocal 时,我们应具备怎样的谈资?

ThreadLocalMap的构造方法,对table数组进行了初始化,每个数组单元对应一个键值对。

ThreadLocalMap的set(),ThreadLocal的set()实际上调用的就是这个set().

到这里我们应该就能明白, ThreadLocalMap其实就是一个容量为16的Entry类型数组,数组用来存放键值对,键的类型固定为ThreadLocal,里面还配置了一些方法(如set()、remove())来对数组进行操作。

ThreadLocalMap 大致讲完了,再回到 ThreadLocal。

上文说到 ThreadLocal 为每一个线程都创建了一个 ThreadLocal 变量副本 ,那这是怎么做到的呢,来看下 ThreadLocal 的源码。

当面试官问到 ThreadLocal 时,我们应具备怎样的谈资?

这是ThreadLocal的set(),它调用的是 ThreadLocalMap 的set().

它先获取当前调用set()的 线程 ,接着获取该线程的 ThreadLocalMap

如果 ThreadLocalMap 已经被实例化了,那么就把我们在代码中定义的ThreadLocal对象作为key,将传进去的参数作为value,以key-value的形式保存到 ThreadLocalMap 的Entry数组中。

如果 ThreadLocalMap 还没有被实例化,那么就创建一个 ThreadLocalMap .

到这里我相信大家应该能明白ThreadLocal是如何为每一个线程创建一个变量副本了的。

接着看ThreadLocal的get()和remove().

当面试官问到 ThreadLocal 时,我们应具备怎样的谈资?

它调用的是Entry的getEntry()。逻辑与set()类似,取值时会确保取到的是当前线程设置的值,如果值为空,那么就返回null.

当面试官问到 ThreadLocal 时,我们应具备怎样的谈资?

也好理解,就不多说了。

我们再用一幅图表示 线程、ThreadLocalMap和ThreadLocal 的关系。

当面试官问到 ThreadLocal 时,我们应具备怎样的谈资?

每个 线程 都有一个 ThreadLocalMap ,ThreadLocalMap里面是Entry数组,每个数组单元用来存放一个以 ThreadLocal 为键的键值对。

面试官:有点东西啊,那你知道ThreadLocal在set()时发生哈希冲突怎么办吗

(自信一点:想难倒我是不存在的)

数据是以键值对方式存进Entry数组的,在存入时会根据键(ThreadLocal)的哈希值,找到它所存放的位置,但这样有时会出现哈希冲突,至于如何应对哈希冲突,看源码就知道了。

当面试官问到 ThreadLocal 时,我们应具备怎样的谈资?

  1. 如果该位置是空的,那么直接将键值对存储;

  2. 若不为空且两个键相同,那么新值换旧值;

  3. 若不为空且两键不相同,那只能找下个空位置了。

面试官:在实际应用中你有没有考虑过ThreadLocal的内存泄露

(还问?不用回家吃晚饭了吗...)

简单来说,内存泄漏是指对象已经无用但长期得不到回收。

ThreadLocalMap存储数据时我们是存储的键值对,键和值都是对象,那么内存泄漏很可能会发生在键值对上,那么我们来看源码。

当面试官问到 ThreadLocal 时,我们应具备怎样的谈资?

Entry是一个静态内部类我们已经说过了,但这次发现: Entry 是继承的 WeakReference ,并且只绑定了 ThreadLocalWeakReference表示 弱引用对象)。

如果一个对象被弱引用关联,那么在GC时会被回收掉。

显然键值对中的键(ThreadLocal)不用担心发生内存泄漏,因为它被弱引用关联。

但是value就不同了,它是强引用,如果该键值对所属线程一直在运行,那么value对象就可能一直得不到回收,于是发生内存泄漏。

防止内存泄漏最直接的方法就是使用完变量后调用ThreadLocal的 remove() ,remove()实际是将对象的引用置为null,这样一来没有引用指向这个对象,该对象就会被JVM判定为垃圾并在GC时回收掉。

面试官:不用说了,大哥,明天能来上班吗,月薪200k

(开个玩笑,手动狗头)

当面试官问到 ThreadLocal 时,我们应具备怎样的谈资?

原文  https://mp.weixin.qq.com/s/BFNIDxWGJJy_NkIdi9Z7uQ
正文到此结束
Loading...