面试官:“先问一个问题,如何在多线程的环境下保证数据不被其余线程修改?”java
能够把这个数据用 ThreadLocal
封装一下面试
面试官:“噢,那你说一下 ThreadLocal 是什么吧,它和用锁有什么不同?”编程
ThreadLocal
提供了线程隔离的变量,它通常以 private static
的形式出现,能够视做类中的全局变量,只不过每一个线程拥有本身的变量数据。 至于它和锁有什么不同,我以为这个问题就不太好,由于它们根本就不是一类东西。对于数据使用方面来讲,锁主要解决的是并发环境下数据竞争的问题,而 ThreadLocal
根本就不是解决并发问题的,由于自己就不存在并发的业务。简单来讲,ThreadLocal
就比如身份证,咱们能够拿身份证去网吧,去坐高铁,去住酒店,但咱们每一个人都是用的本身的身份证,不存在什么公共身份证你们抢着用。数据结构
面试官:“噢,看来你对使用场景很明白嘛,那你讲讲要怎么保证线程隔离的呢?”多线程
这个简单啊,谁的数据就直接给它本身保管就行了嘛,对应的在 Java 中就是把线程隔离的数据存在这个线程对应的 Thread
实例中。并发
面试官:“具体一点?”spa
JDK 的作法是实际上是每个 Thread
类中都保存了一个 ThreadLocalMap
,以 ThreadLocal
为 key,保存的数据为 value, 这样每一个线程就能有多个线程隔离的数据啦。如下面这段代码为例线程
public class Main {
private static ThreadLocal<String> id = new ThreadLocal<>();
private static ThreadLocal<String> phone = new ThreadLocal<>();
public static void main(String[] args) {
Thread t1 = new Thread(()-> {
id.set("2");
phone.set("654321");
});
t1.start();
id.set("1");
phone.set("123456");
}
}
复制代码
对应的结构图以下所示:code
所以,对 ThreadLocal
的读写,其实就是对 Thread
中的 ThreadLocalMap
的读写cdn
面试官:“能够,你刚才说到 Map 的读写了吧,那 ThreadLocalMap 和 HashMap 有什么差异呢?”
须要注意的是 ThreadLocalMap
虽然也叫 Map,但并无继承集合类中的 Map
接口,最大的差异再处理冲突上是不一样的,集合类中的 Map 使用链地址法来处理冲突,而 ThreadLocalMap
使用的是开放寻址法,简单的说就是若是冲突了,就找下一个索引的 Entry
,直到找到空的为止,对应的代码为:(PS:若是学过数据结构的话应该知道散列表的几种冲突处理,就不用看下面代码啦)
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 使用hash来肯定索引
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
// 若是 tab[i] 已经被占了,并且不是更新操做(即 k != key),那么将索引+1,看看是否被占了
// e = tab[i = nextIndex(i, len)]
// private static int nextIndex(int i, int len) {
// return ((i + 1 < len) ? i + 1 : 0);
// }
}
...
}
复制代码
面试官:"看来你还看过一些源码嘛,那 ThreadLocalMap 中 Entry 的 key 对 ThreadLocal 是弱引用你总应该知道的吧?"
固然,由于 ThreadLocal
实例通常有两个引用地方,一个是声明 ThreadLocal
的地方,还一个是 ThreadLocalMap
中 key 的引用,这就致使了一个问题,若是除了 ThreadLocalMap
外再也不有引用的话,那么 ThreadLocal
实例仍是没法释放,可是这个实例也不再会被访问到了,也就是内存泄露。所以将其设为弱引用的话,若是没有其余外部强引用后也能被回收。
面试官:“虽然 ThreadLocal 被回收了,可是 Entry 还在啊,里面的 value 引用也是个内存泄露啊!”
天然是有相应对策的,在调用 ThreadLocal
的 get()
、set()
等方法时可能会清除ThreadLocalMap
中 key 为 null
的 Entry
对象。不过仍是建议在编程时使用 remove()
把不用了数据删掉吧。另外就像开头里说的,建议是搭配 private static
使用的,毕竟卸载类仍是比较少见的吧,既然和线程同寿命,也就不存在泄露不泄露的问题了哈~
面试官:“嗯,那 ThreadLocal 就聊到这里吧~”