ThreadLocal

线程隔离机制。

ThreadLocal实际是一种线程隔离机制,也是为了保证在多线程环境下对于共享变量的访问安全性。

static ThreadLocal<Integer> local = new ThreadLocal<Integer>() {
  protected Integer initialValue() {
    return 0; //初始化一个值
  }
};

public static void main(String[] args) {
  Thread[] thread = new Thread[5];
  for (int i = 0; i < 5; i++) {
    thread[i] = new Thread(() -> {
      int num = local.get(); //获得的值都是0
      local.set(num += 5); //设置到local中
      System.out.println(Thread.currentThread().getName()+"-"+num);
    });
  }
  for (int i = 0; i < 5; i++) {
    thread[i].start();
  }
}

ThreadLocal原理分析

set方法实现

public void set(T value) {
  Thread t = Thread.currentThread();
  ThreadLocalMap map = getMap(t);
  if (map != null)
    map.set(this, value);
  else
    createMap(t, value);
}

第一次当map位空的时候会创建一个ThreadLocalMap。ThreadLocalMap是一个懒加载的方式。

void createMap(Thread t, T firstValue) {
  t.threadLocals = new ThreadLocalMap(this, firstValue);
}

当map不为空的时候的执行逻辑:

  • 根据key的散列哈希计算Entry的数组下标
  • 通过线性探索探测从i开始往后一直遍历到数组的最后一个Entry
  • 如果map中的key和传入的key相同,表示该数据已经存在,直接覆盖
  • 如果map中的key为空,则用新的key、value覆盖,并清理key=null的数据
  • rehash扩容
private void set(ThreadLocal<?> key, Object value) {

  Entry[] tab = table;
  int len = tab.length;
  //根据哈希码和数组长度求元素放置的位置,即数组下标 这个的初始值为0
  int i = key.threadLocalHashCode & (len-1);  // & 后面有这个标志的讲解

  //从i开始往后一直遍历到数组最后一个Entry(线性探索)
  for (Entry e = tab[i];
       e != null;
       e = tab[i = nextIndex(i, len)]) {
    ThreadLocal<?> k = e.get();

    //如果key相等,覆盖value
    if (k == key) {
      e.value = value;
      return;
    }

    //如果key为null,用新key、value覆盖,同时清理历史key=null的陈旧数据(弱引用)
    if (k == null) {
      replaceStaleEntry(key, value, i);
      return;
    }
  }

  tab[i] = new Entry(key, value);
  int sz = ++size;
  //如果超过阈值,就需要扩容了
  if (!cleanSomeSlots(i, sz) && sz >= threshold)
    rehash();
}
线性探测,是用来解决hash冲突的一种策略。它是一种开发寻址的策略。
例如hash表,它是根据key进行直接访问的数据结构,也就是说我们可以通过hash函数把key映射到hash表中的一个位置来访问记录,从而加快查询的速度。存放记录的数据就是hash表(散列表)
当我们针对一个key通过hash函数计算产生的一个位置,在hash表中已经被另一个键值对占用时,那么线性探测就可以解决这个冲突,这里分为两种情况。
- 写入:查找hash表中离冲突单位最近的空闲单元,把新的键值插入到这个空闲单元
- 查找:根据hash函数计算的一个位置开始往后查找,找到与key对应的value或者找到空的单元。

replaceStaleEntry

分析清理的过程和替换的过程。

private void replaceStaleEntry(ThreadLocal<?> key, Object value,
                               int staleSlot) {
  Entry[] tab = table;
  int len = tab.length;
  Entry e;

  //向前扫描,查找最前面一个无效的slot
  int slotToExpunge = staleSlot;
  for (int i = prevIndex(staleSlot, len);
       (e = tab[i]) != null;
       i = prevIndex(i, len))
    //通过循环遍历,可以定位到最前面一个无效的slot
    if (e.get() == null)
      slotToExpunge = i;

  //从i开始往后一直遍历到数组最后一个Entry(线性探索)
  for (int i = nextIndex(staleSlot, len);
       (e = tab[i]) != null;
       i = nextIndex(i, len)) {
    ThreadLocal<?> k = e.get();

    //找到匹配后的key以后
    if (k == key) {
      //更新对应slot的value值
      e.value = value;

      //与无效的slot进行交换
      tab[i] = tab[staleSlot];
      tab[staleSlot] = e;

      // 如果最早的一个无效的slot和当前的staleSlot相等,则从i作为清理的起点
      if (slotToExpunge == staleSlot)
        slotToExpunge = i;
      //从slotToExpunge开始做一次连续的清理
      cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
      return;
    }

  //如果当前的slot已经失效,并且向前扫描过程中没有无效slot,则更新slotToexpunge为当前位置
    if (k == null && slotToExpunge == staleSlot)
      slotToExpunge = i;
  }

  //如果key对应在entry中不存在,则直接放在一个新的entry
  tab[staleSlot].value = null;
  tab[staleSlot] = new Entry(key, value);

  // 如果有任何一个无效的slot,则做一次清理
  if (slotToExpunge != staleSlot)
    cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
}

0x61c88647 斐波那切数列

private static final int HASH_INCREMENT = 0x61c88647;
public static void main(String[] args) {
  magicHash(16);
  magicHash(32);
}
private  static void magicHash(int size){
  int hashCode=0;
  for(int i=0;i<size;i++){ 
    hashCode=i*HASH_INCREMENT+HASH_INCREMENT; 
    System.out.print((hashCode&(size-1))+" ");
  }
  System.out.println(""); 
}
7  14  5  12  3  10  1  8  15  6  13  4  11  2  9  0
7  14  21  28  3  10  17  24  31  6  13  20  27  2  9  16  23  30  5  12  19  26
1  8  15  22  29  4  11  18  25  0

线性探测

用来解决hash冲突的一种策略

  • 写入,找到发生冲突最近的空闲单元
  • 查找,从发生冲突的位置,往后查找

ThreadLocal使用弱引用的好处

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}
-  弱引用的定义是:如果一个对象仅被一个弱引用指向,那么当下一次GC到来时,这个对象一定会被垃圾回收器回收掉;
-  通过源码分析,我们可以看到ThreadLocalMap里面的Entry节点的key值是弱引用类型,当设置key=null的时候,当ThreadLocal使用get、set方法的时候都会去清理这些key为空的数据;
-  如果key为强引用的话,如果当前的ThreadLocalDemo.threadLocal = null,去掉了threadLocal的引用,但此时还存在thread->threadLocalMap->entry->key(threadLocal)的引用,
这样的话除非threadLocal使用结束,不然的话是无法释放掉entry的值的,会造成内存泄漏的情况;
-  如果使用threadLocal线程池的时候,会导致entry内的value不能释放,也会导致内存的泄漏。

ps:

&是什么意思?

&在 java 中做与运算,& 是所有的2进制位数“与”出的最终结果,“与”的规则是两者都为1时才得1,否则就得0

例如

132&15 = ?

答案:4

why?

阿拉伯数字(十进制):132     二进制:10000100

阿拉伯数字(十进制):15      二进制:0000 1111(计算器转换应该是1111,因为两个二进制进行运算时,需要在位数少的前面补零-补码操作)

10000100 & 0000 1111 = 0100   //4

(0100 & 1111 = 0100 )真正的运算是这样的,根据&的规则则取末尾是0的0100

结论:
①、当两个数末尾是1和0时,根据规则取0的数
②、当两个数末尾都是1时,根据规则取数小的,例如1111和0101就是0101
posted @ 2020-08-14 22:46  snail灬  阅读(114)  评论(0编辑  收藏  举报