How much do you know about ThreadLocal?

What is ThreadLocal

 其实对于ThreadLocal并不陌生,在多线程的场景下,应该是都有使用的, ThreadLocal实际上一种线程隔离机制,也是为了保证在多线程环境下对于共享变量的访问的安全性。线程私有的,那么也就存在线程共享。
 1、因为每个 Thread 内有自己的实例副本,且该副本只能由当前 Thread 使用。这是也是 ThreadLocal 命名的由来。
 2、既然每个 Thread 有自己的实例副本,且其它 Thread 不可访问,那就不存在多线程间共享的问题。
 3、ThreadLocal 变量通常被private static修饰。当一个线程结束时,它所使用的所有 ThreadLocal 相对的实例副本都可被回收

ThreadLocal usage scenarios

个人认为技术的选择一定是基于某个业务场景下的。脱离了业务为背景的技术都是空谈的,都是不缺实际的。因此也就没有所谓的技术过时一说,只是说某个技术不适用了当前的这个业务场景了。所以说选择什么样的技术,一大部分是取决于业务场景的。就那个ThreadLocal来说,首先必要的就是淡当然要了解该技术的原理了。
  1、每个线程需要有自己单独的实例
  2、实例需要在多个方法中共享,但不希望被多线程共享

usage

入门才是关键,对于一个新的知识,先入门才能更好研究(在心里找到平衡),只有先通过使用,结合其特点,在来研究为啥会有这样的效果?
 说白了就是,每个单独的线程独自管理自己线程中的内容
package com.yypdemo.example.ThreadLocalDemo;
/**
 * @author lamb-yyp
 * @version 1.0
 * @date 2020/9/13 13:28
 */
public class Demo1 {
    
    
     static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
     private static void setThreadLocal(){
    
    
         threadLocal.set(100);
     }
    public static void main(String[] args) {
    
    
         // Demo1.setThreadLocal();
         Thread[] threads = new Thread[5];
         for(int i=0;i< 5; i++){
    
    
            Thread thread = new Thread(()->{
    
    
                Demo1.setThreadLocal(); //初始化 (或者写在静态类中)
                int num = threadLocal.get(); // 线程独享
                threadLocal.set(num+1);         System.out.println(Thread.currentThread().getName()+" - "+ threadLocal.get());
            });
             threads[i] = thread;
             thread.setName("线程"+i);
         }
         for(int i=0;i< 5;i++){
    
    
             threads[i].start();
         }
    }
}

Results of the
Insert picture description here

Fundamental

说到原理,我们还是根据用法上来作为突破口即(set,get方法)

set method (will determine whether there is an initialized array) Insert picture description here
initialization ()
Insert picture description here
array initialization (first time in, lazy loading)

  ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
    
     // INITIAL_CAPACITY = 16
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 通过hash函数,计算出下标i
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            // 计算出扩容因子16*0.75 = 12
            setThreshold(INITIAL_CAPACITY);
        }
        
/**
         * Set the value associated with key.
         *
         * @param key the thread local object
         * @param value the value to be set
         */
 private void set(ThreadLocal<?> key, Object value) {
    
    

// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
 // it is to replace existing ones, in which case, a fast
 // path would fail more often than not.

  Entry[] tab = table; // 获取数组
 int len = tab.length; // 获取数组长度
 int i = key.threadLocalHashCode & (len-1); //计算当前可以的下标
 for (Entry e = tab[i];  e != null;  e = tab[i = nextIndex(i, len)]) {
    
     //从i 往后遍历,遍历到最后一个Entry(纯粹的线性探索,相对比较耗时)
                ThreadLocal<?> k = e.get();
                if (k == key) {
    
     // 找到相同的,覆盖值
                    e.value = value;
                    return;
                }

                if (k == null) {
    
     // 如果为null ,用新值覆盖
                    //并且清除为key = null的旧数据(弱应用)
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            tab[i] = new Entry(key, value);
            int sz = ++size;
            // 达到阀值,就需要扩容了
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

Weak reference
Insert picture description here

private void replaceStaleEntry(ThreadLocal<?> key, Object value,  // 值
int staleSlot  //无用的数值下标
) {
    
    
            Entry[] tab = table;
            int len = tab.length;
            Entry e;
            // Back up to check for prior stale entry in current run.
            // We clean out whole runs at a time to avoid continual
            // incremental rehashing due to garbage collector freeing
            // up refs in bunches (i.e., whenever the collector runs).
            int slotToExpunge = staleSlot;
            // 找到最近的 一个无效的Slot
            for (int i = prevIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = prevIndex(i, len))
                if (e.get() == null)
                    slotToExpunge = i;
// Find either the key or trailing null slot of run, whichever
 // occurs first
      // 从i往后遍历,找到替换
    for (int i = nextIndex(staleSlot, len);
                 (e = tab[i]) != null;
                 i = nextIndex(i, len)) {
    
    
                ThreadLocal<?> k = e.get();

                // If we find key, then we need to swap it
                // with the stale entry to maintain hash table order.
                // The newly stale slot, or any other stale slot
                // encountered above it, can then be sent to expungeStaleEntry
                // to remove or rehash all of the other entries in run.
        if (k == key) {
    
    
       e.value = value;
           tab[i] = tab[staleSlot];
                     //与无效的sloat进行交换
                    tab[staleSlot] = e;

     // Start expunge at preceding stale entry if it exists
  如果最早的一个无效的slot和当前的staleSlot相等,则从i作为清理的起点
        if (slotToExpunge == staleSlot)
                        slotToExpunge = i;
       cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
                    return;
                }
   // If we didn't find stale entry on backward scan, the
   // first stale entry seen while scanning for key is the
   // first still present in the run.
    if (k == null && slotToExpunge == staleSlot)
                slotToExpunge = i;
        }
 // If key not found, put new entry in stale slot 
             // 如果没有,将新的ksy-value 插入数组
            tab[staleSlot].value = null;
            tab[staleSlot] = new Entry(key, value);
            // If there are any other stale entries in run, expunge them
            if (slotToExpunge != staleSlot)
         cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
        }

Expansion

/**
         * Re-pack and/or re-size the table. First scan the entire
         * table removing stale entries. If this doesn't sufficiently
         * shrink the size of the table, double the table size.
         */
private void rehash() {
    
    
      expungeStaleEntries();
  // Use lower threshold for doubling to avoid hysteresis
     if (size >= threshold - threshold / 4)
         resize();
     }
 /**
   * Expunge all stale entries in the table.
   */
 private void expungeStaleEntries() {
    
    
            Entry[] tab = table;
      int len = tab.length;
      // 遍历 将无效的 剔除掉
      for (int j = 0; j < len; j++) {
    
    
                Entry e = tab[j];
          if (e != null && e.get() == null)
             //向后遍历 循环
             expungeStaleEntry(j);
       }
  }
 /**
   * Double the capacity of the table. 
  */
 private void resize() {
    
    
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2; // 扩容为原来的2倍
            Entry[] newTab = new Entry[newLen];
            int count = 0;
            // 将原表的数据迁移至新表中
         for (int j = 0; j < oldLen; ++j) {
    
    
                Entry e = oldTab[j];
              if (e != null) {
    
    
                    ThreadLocal<?> k = e.get();
              if (k == null) {
    
    
                        e.value = null; // Help the GC
                } else {
    
    
            int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }
            // 计算新的扩容因子
            setThreshold(newLen);
            size = count;
            table = newTab;
        }  

What is linear detection?

用来解决hash冲突的一种策略.就是根 「据初始 key 的hashcode值确定元素在 table 数组中的位置,如果发现这个位置上已经有其他 key 值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置」 。 ThreadLocalMap 解决 Hash 冲突的方式就是简单的步长加 1 或减 1 ,寻找下一个相邻的位置
 1、写入 , 找到发生冲突最近的空闲单元
 2、查找, 从发生冲突的位置,往后查找 
// 斐波那契数列 (为了使不同 hash 值发生碰撞的概率更小,尽可能促使元素在哈希表中均匀地散列在表中)
  private static final int HASH_INCREMENT = 0x61c88647;
 public static void main(String[] args) {
    
    
         // Demo1.setThreadLocal();
        hashTableIndex(16);
    }
public static void hashTableIndex(int size){
    
    
         // 生成hashcode间隙为这个魔数,可以让生成出来的值或者说ThreadLocal的ID较为均匀地分布在2的幂大小的数组中。
        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

Why do you need a power of 2 to calculate subscripts?

There is a great god who wrote it better, so I won’t go into details, and quote: Why is it a power of 2

Final summary

大伙应该都发现了,由于用到的线性探索,存在大量的循环遍历,这样是相对来说是很消耗性能的。

ThreadLocal memory leak

 在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是GCRoot可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存

Suggest

1、使用ThreadLocal,建议用static修饰 static ThreadLocal headerLocal = new ThreadLocal();
2、使用完ThreadLocal后,及时执行remove操作,避免出现内存溢出情况。

Guess you like

Origin blog.csdn.net/u010200793/article/details/108560299