Java多线程(四) —— ThreadLocal 解析

一、应用场景和使用

   场景:当一个全局共享变量/对象被多个线程调用时,会相互影响。当我们需要线程级别的资源隔离时就可以用到ThreadLocal。

public class ThreadLocalDemo {
    private  static   int  num=0;
    public static void main(String[] args) {
        Thread[] thread=new Thread[5];
        for (int i=0;i<5;i++){
            thread[i]=new Thread(()->{
                num+=5;
                System.out.println(Thread.currentThread().getName()+"-"+num);
            });
        }
        for (int i = 0; i < 5; i++) {
            thread[i].start();
        }
    }
}

  使用:

public class ThreadLocalDemo {
    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);
                local.remove();
            });
        }
        for (int i = 0; i < 5; i++) {
            thread[i].start();
        }
    }
}

 二、原理

  1.set方法:根据当前线程是否有ThreadLocalMap 分两部分     

    1)  第一次初始化ThreadLocalMap     

        过程:​​​​​​   

             a. 根据key的散列哈希计算Entry的数组下标

             b.通过线性探索探测从i开始往后一直遍历到数组的最后一个Entry

             c.如果map中的key和传入的key相等,表示该数据已经存在,直接覆盖; 如果map中的key为空,则用新的keyvalue覆盖,并清理key=null的数据

             d.rehash扩容

private void set(ThreadLocal<?> key, Object value) {

Entry[] tab = table; int len = tab.length;

// 根据哈希码和数组长度求元素放置的位置,即数组下标
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表是根据key进行直接访问的数据结构,我们通过hash函数把key映射到hash表中的一个位置来访问记录,从而加快查找的速度。存放记录的数据  就是hash表(散列表); 当我们针对一个key通过hash函数计算产生的一个位置,在hash表中已经被另外一个键值对占用时,那么线性探测就可以解决这个冲突,这里分两种情况。

        写入:查找hash表中离冲突单元最近的空闲单元,把新的键值插入到这个空闲单元

        查找:根据hash函数计算的一个位置处开始往后查找,指导找到与key对应的value或者找到空的单元。

2)已存在,清理的过程和替换过程

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)) 
if (e.get() == null)
//通过循环遍历,可以定位到最前面一个无效的slot
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) {
e.value = value;//更新对应slot的value值

//与无效的sloat进行交换
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对应的value在entry中不存在,则直接放一个新的entry tab[staleSlot].value = null;
tab[staleSlot] = new Entry(key, value);

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

   注意:ThreadLocalMapkey为弱引用 value为强引用 所以会引起内存泄漏使用完Treadlocal最好手动remove

三、为什么要用弱引用

      每个thread中都存在一个ThreadLocalMap.Map中的key为一个threadlocal实例.

      每个key都弱引用指向threadlocal.当把threadlocal实例置为null以后,没有任何强引用指向threadlocal实例,所以threadlocal就可以顺利被gc回收。假如每个key都强引用指向threadlocal,也就是上图虚线那里是个强引用,那么这个threadlocal就会因为和entry存在强引用无法被回收!造成内存泄漏 ,除非线程结束,线程被回收了,map也跟着回收。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值