zoukankan      html  css  js  c++  java
  • 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
    
  • 相关阅读:
    Root resource classes
    web.xml文件的作用
    Mac OS X 快捷键(完整篇) 转载
    Eclipse快捷键大全(转载)
    mac os 下如何清除/切换svn eclipse插件的用户
    N-Queens II
    接口是永远不能被实例化的
    Chpater 10: Sorting
    Java 理论和实践: 了解泛型
    EMQ X + ClickHouse 实现物联网数据接入与分析
  • 原文地址:https://www.cnblogs.com/snail-gao/p/13506045.html
Copyright © 2011-2022 走看看