面试官:你能说一下java中有哪些机制能保证线程安全吗
据我了解的,java中保证线程安全的机制有锁和ThreadLocal。首先说下锁...(被面试官强行打断(不要问我为什么要强行打断))
面试官:你先讲讲什么是ThreadLocal
ThreadLocal使用起来不难,看下面代码。
示例代码中开启了两个线程:主线程和一个子线程。只定义了一个ThreadLocal变量,两条线程中分别调用ThreadLocal的set()进行赋值,最后打印结果。来看下输出。
面试官:那你能解释一下ThreadLocal的工作原理吗
(涉及到底层的就有点难度了,不过问题不大,我们先假装思考几秒......)
但是ThreadLocalMap并没有实现Map接口,它实际是初始化了一个大小为16的Entry类型数组,每个数组单元存储一个键值对(key-value),键(key)的类型为ThreadLocal,这些可以通过源码看到。
容量大小为16,table类型为Entry。(Entry是ThreadLocalMap的内部类)
ThreadLocalMap的构造方法,对table数组进行了初始化,每个数组单元对应一个键值对。
ThreadLocalMap的set(),ThreadLocal的set()实际上调用的就是这个set().
到这里我们应该就能明白,ThreadLocalMap其实就是一个容量为16的Entry类型数组,数组用来存放键值对,键的类型固定为ThreadLocal,里面还配置了一些方法(如set()、remove())来对数组进行操作。
ThreadLocalMap大致讲完了,再回到ThreadLocal。
这是ThreadLocal的set(),它调用的是ThreadLocalMap的set().
如果ThreadLocalMap已经被实例化了,那么就把我们在代码中定义的ThreadLocal对象作为key,将传进去的参数作为value,以key-value的形式保存到ThreadLocalMap的Entry数组中。
如果ThreadLocalMap还没有被实例化,那么就创建一个ThreadLocalMap.
到这里我相信大家应该能明白ThreadLocal是如何为每一个线程创建一个变量副本了的。
接着看ThreadLocal的get()和remove().
每个线程都有一个ThreadLocalMap,ThreadLocalMap里面是Entry数组,每个数组单元用来存放一个以ThreadLocal为键的键值对。
面试官:有点东西啊,那你知道ThreadLocal在set()时发生哈希冲突怎么办吗
(自信一点:想难倒我是不存在的)
数据是以键值对方式存进Entry数组的,在存入时会根据键(ThreadLocal)的哈希值,找到它所存放的位置,但这样有时会出现哈希冲突,至于如何应对哈希冲突,看源码就知道了。
面试官:在实际应用中你有没有考虑过ThreadLocal的内存泄露
(还问?不用回家吃晚饭了吗...)
简单来说,内存泄漏是指对象已经无用但长期得不到回收。
Entry是一个静态内部类我们已经说过了,但这次发现:Entry是继承的WeakReference,并且只绑定了ThreadLocal(WeakReference表示弱引用对象)。
如果一个对象被弱引用关联,那么在GC时会被回收掉。
显然键值对中的键(ThreadLocal)不用担心发生内存泄漏,因为它被弱引用关联。
但是value就不同了,它是强引用,如果该键值对所属线程一直在运行,那么value对象就可能一直得不到回收,于是发生内存泄漏。
面试官:不用说了,大哥,明天能来上班吗,月薪200k
(开个玩笑,手动狗头)