ThreadLocal您了解多少?

什么是ThreadLocal

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

ThreadLocal使用场景

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

用法

入门才是关键,对于一个新的知识,先入门才能更好研究(在心里找到平衡),只有先通过使用,结合其特点,在来研究为啥会有这样的效果?
 说白了就是,每个单独的线程独自管理自己线程中的内容
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();
         }
    }
}

执行结果
在这里插入图片描述

基本原理

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

set方法 (会判断是否有初始化化数组)在这里插入图片描述
初始化()
在这里插入图片描述
数组初始化(第一次进来,惰性加载)

  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();
        }

弱引用
在这里插入图片描述

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);
        }

扩容

/**
         * 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;
        }  

什么是线性探测?

用来解决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

为啥计算下标都是需要2的次幂?

有位大神写的比较好,我就不赘述了,引用下:为什么是2的次幂

最后的总结

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

ThreadLocal的内存泄露

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

建议

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值