zoukankan      html  css  js  c++  java
  • ThreadLocal 面试

    问:

      说一下 ThreadLocal 原理, java8 

    答:

      java8, 每个线程对应的 Thread 对象内部有一个 ThreadLocals 字段, 这个字段指向堆中的一个ThreadLocalMap.

      这个 ThreadLocalMap 内部存储的是,当前线程与其他 ThreadLocal 对象关联的数据。

      Thread 这个线程对象,里面有一个 map 对象,这个map 存的是ThreadLocal 对象关联的数据。

    问:

      它是怎么做到线程,互不干扰的。

    答:

      线程有一个自己的 THreadLocalMap 存储数据。

      线程访问某个 ThreadLocal 对象 get 方法时,会检测 当前线程 map 内部是否有 key 为这个 ThreadLocal 对象的 Entry 数据。

      如果没有,这个 ThreadLocal 的 initial Value 方法 会创建一个 Entry 然后存放到这个 ThreadLocalMap 

    问:

      jdk 1.8 之前的 版本怎么设计的

    答:

      老版本会在,TreadLocal 里面维护一个大 map, 所有线程的变量都会维护在一个 map 里面。

    问:

      jdk 8 和 之前的版本有什么优势。

    答:

      老版本维护一个大的 map, 线程多的话, 这个map 会很大。不利于维护。

      新的版本。每个线程都会维护自己的数据,当线程被销毁的时候,线程对应额 ThreadLocalMap 在下次 GC 的时候被回收了。

      还有这个 ThreadLocalMap 中的 Entry 存的 key 是弱引用,如果 ThreadLocal 对象被回收的话,是不影响的即弱引用不参与 root 算法。

    问:

      使用的 Hash 是从 Object 继承下来的 hashCode 方法吗?

    答:

      不是,这个是自己重写的,用一个黄金分割数来分割,均匀的分布在 Entry 数组里面。

      如果 从 Object 继承的 HashCode 计算出来的 hash 值是不均匀的。 如果用黄金分割数,分配 hash 值,映射到散列表内部就很均匀。

      比如长度为 16 分配四个,就 table[0] table[4] table[8] table[12], 反映到散列表,就很均匀。

    问:

      为什么 TheadLocalMap 使用 自定义 map, 而不是 jdk 的 HashMap

    答:

      重写的话,可以把这个 key 为限定为特有类型,就是 ThreadLocal 这个类型,key 是弱以用。

      TheadLocal 这个写数据和查数据过程中,有清理过期数据的策略。能够将过期数据清理掉,解决了内存泄漏问题。

        TheadLocal 的 value 的引用, 如果是对应的数据是过期的话,就会被干掉,   

     问:

      每个线程的 ThreadLocalMap 对象是什么时间创建的

    答:

      每个线程的 ThreadLocalMap 是延迟初始化的

      第一次调用get 或者 set 时候,检测当前线程是否绑定 ThreadLocalMap,

      如果有就继续 get 或者 set, 如果没有, 就先创建。

    问:

      那么这个线程会不会被多次创建?

    答:

      在线程的生命周期内,ThreadLocalMap 只会初始化一次

    问:

      这个 map  初始化长度是多少

    答:

      16

    问:

      为什么这个长度,是 2 的次方数

    答:

      和 hashMap 一样,方便 hash 寻址。 因为 2 的次方数减一之后转变为 二级制由 1 组成,

      如果数值与二进制位与运算,得到的数,大于等于0 且小于等于这个二进制数值,比取模算法,即%,效率高很多。

      即 因为使用的是 位运算,所以效率高。

    问:

      扩容阈值时多少, 它达到扩容阈值一定会扩容吗

    答:

      entry 数组的 2/3。

      但是不一定会扩容,它会 rehash 一次, 调用 rehash 方法。

      全量扫描整个散列表的逻辑,把过期数据清理掉,

      如果全量扫描完后,当前散列表的数据仍然达到这个扩容阈值 3/4, 才真正进行扩容.

    问:

      这个扩容算法是什么

    答:

      首先,创建一个新的数组,长度是当前散列表数组的两倍,迭代老的数组,将其中的数组,按照  hash 算法放入,新的数组里边。

      迭代完后,这个数组就迁移完了。然后更新 ThreadLocalMap 对象的散列表引用。它会指向这个新的数组引用,扩容基本完成。

      (细节) 扩容之后,还会重新计算下次扩容的阈值。

    问:

      ThreadLocal Map Get 的逻辑

    答:

      根据这个 ThreadLocal 对象的 hash 值 按位与  , 当前数组长度减一 得到一个 index 

      这个散列表数组中,下标就是这个 index 的元素,可能就是要查找的数据。如果查找的地方,发生过 hash 冲突,因为 ThreadLocal

      内部类,Entry 没有 next 这个字段,ThreadLocal 采用的是 hash冲突后,线性的找到一个合适的位置去写数据。

      如果 get 没有命中的话,就要继续向后查找,直到找到这个数据或者碰到 null 就结束。同时,还会遍历当前数据是否过期。

    问:

      假如第一次 get 没有 get 到, 如果查找过程中碰到过期数据,怎么处理

    答:

      首先,过期数据是什么。

     ThreadLocal 内部存的 是 Entry, Entry 有两个字段,分别是 key 和 value, key 是一个弱引用。指向内存,已经限定类型的 ThreadLocal 对象。

     value 就是当前线程的关联对象, 当 key 对应的 ThreadLocal 对象被 GC 回收后,

     以为 key 是 弱引用,所以 key 的 get 方法 可能会 get 一个 指向 null 的一个引用,就这个 Entry 是过期的。

        再说一下,get 查询过程,碰到 过期数据怎么处理

      先会触发 “探测式” 过期数据回收逻辑, 就是从当前桶位开始向后迭代, 碰到 key == null 的 Entry 设置为 nll,一直迭代到 slot == null 为止。

      向下迭代过程中如果遇到正常数据,会根据 key 重新重新计算一个 index, 如果等于,index 是否等于 当前位置,如果等于,就相当于啥也不做, 

      因为写入时,可以认为没有发生过 hash 冲突。如果重新计算的 index 不等于当前位置,说明发生了 hash 冲突, 当前数据的slot之前可能 有过期数据被干掉。

      正常数据需要重新寻找一个更合适的位置去存放数据,这个位置理论上更接近或等于于正确的 index。

    问:

      Set 的流程

    答:

        根据 key 找到 对应下标的 slot ,如果 slot 为 null , 说明当前 set 方法是,新添加数据的逻辑。

      如果这个 slot 不是 null, 那情况就比较复杂。两种情况

      第一种,添加新的逻辑,但是发生 hash 冲突, 就线性找到可以使用的 slot 然后插入。

      第二种,是更新的逻辑,如果过程查到 key 和 set 的 key 一致的话,发生Entry替换 value,

          如果查找到过期数据,就做一个替换逻辑。

    问:

      set 过程中,替换过期数据的逻辑是怎么样。这个挺难的,还记得吗

    答:

      它会以当前位置的下一个桶位开始向后去查找,直到碰到 null 或者 key  一致才会停止。

       第一种情况,就是碰到当key 一致的时候,那么set 的这个数据,直接就更新到当前这个桶位的这个Entry,就可以了,就更新逻辑。

            然后让,当前的 Entry 与过期的 slot 进行一次互换。

      第二种情况,遍历到null , 也没有找到key 一致的数据,

            那么直接在当前过期桶位直接重写一个Entry 就 ok了,相当于抹除过期数据,将新的数据放到这里。

            还涉及到启发式过期数据的清理的逻辑。

    https://www.bilibili.com/video/BV19C4y1W72V?t=519

      

      

  • 相关阅读:
    【转载】如何保证消息的顺序性?
    【转载】如何保证消息的可靠性传输?
    Java 浅拷贝与深拷贝的区别
    int 与 Integer 的区别
    Systemd
    如何查看systemctl启动服务的日志journalctl
    centos7安装killall命令
    关闭root用户的ssh登录
    react带ts创建
    ts-类型别名、类型断言
  • 原文地址:https://www.cnblogs.com/Jomini/p/13857837.html
Copyright © 2011-2022 走看看