面试官:“我就是要问你 ThreadLocal ”

面试官:“先问一个问题,如何在多线程的环境下保证数据不被其余线程修改?”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 引用也是个内存泄露啊!”

天然是有相应对策的,在调用 ThreadLocalget()set() 等方法时可能会清除ThreadLocalMap 中 key 为 nullEntry 对象。不过仍是建议在编程时使用 remove() 把不用了数据删掉吧。另外就像开头里说的,建议是搭配 private static 使用的,毕竟卸载类仍是比较少见的吧,既然和线程同寿命,也就不存在泄露不泄露的问题了哈~

面试官:“嗯,那 ThreadLocal 就聊到这里吧~”

相关文章
相关标签/搜索