首页 > 其他分享 >ThreadLocal原理探究

ThreadLocal原理探究

时间:2023-04-11 16:38:06浏览次数:35  
标签:ThreadLocalMap 探究 value ThreadLocal 线程 引用 key 原理

四大引用是什么,分别有什么特点: 1 强引用、软引用、弱引用、虚引用 强引用:发生gc的时候,只要对象还有引用,就不会被回收 软引用:发生gc的时候,内存够用就不会回收,内存不够时,就会回收。可以及时的避免oom。 Map<String,SoftReference<BitMap>> imageCache = new HashMap<String,SoftReference<BitMap>>(); 弱引用:发生gc的时候,马上就会回收。 虚引用:虚引用并不会决定对象的生命周期。如果一个对象仅仅持有虚引用,那么它和没有任何引用一样,在任何时候都可能被垃圾收集器回收。它不能单独使用也不通过通过它访问对象。虚引用必须和引用队列(ReferenceQueue)联合使用。 虚引用的目的是跟踪对象被垃圾回收的状态。仅仅是提供了确保对象被finalize之后做某些事情的机制。PhantomReference的get方法总是返回null. 四种引用垃圾回收的场景:   思考: 1 ThreadLocal中的ThreadLocalMap的数据结构与关系? Thread中有一个成员变量ThreadLocalMap。ThreadLocal中有一个静态内部类ThreadLocalMap,ThreadLocalMap中有一个静态内部类Entry.  Entry是对ThreadLocal的弱引用。 2 Entry的key是弱引用,为什么? 每个Thread对象维护着ThreadLocalMap的引用,ThreadLocalMap是ThreadLocal的内部类,用Entry进行存储。 调用ThreadLocal的set方法时,实际是往ThreadLocalMap设置值,key是ThreadLocal对象, value是传进来的对象。 调用ThreadLocal的get方法时, 实际是从ThreadLocalMap中获取值。 ThreadLocal本身并不存储值,它只是以自己作为key从线程的ThreadLocalMap获取value, 正因为如此,ThreadLocal实现了线程隔离,获取当前线程的局部变量值,不受其他线程影响。   ThreadLocal提供线程局部变量,每一个线程在访问ThreadLocal实例的时候(通过其get与set方法)都有自己的,独立的初始化变量副本。Threadlocal实例通常是类中的私有静态字段,使用它的目的是希望将状态与线程关联起来。 //ThreadLocal的方法

public void set(T value) {
       Thread t = Thread.currentThread();
       ThreadLocalMap map = getMap(t);
       if (map != null)
            map.set(this, value); // 使用this引用作为key,既做到了变量相关,又满足key不可变的要求。
       else
           createMap(t, value);
}
//ThreadLocalMap的方法
private void set(ThreadLocal<?> key, Object value) {
           // map中就是使用Entry[]保留所有的entry实例
           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)]) {
               ThreadLocal<?> k = e.get();
               //已经存在则替换旧值
               if (k == key) {
                    e.value = value;
                   return;
               }
               //在设置期间清理哈希表为空的内容,保持哈希表的性质
               if (k == null) {
                   replaceStaleEntry(key, value, i);
                   return;
               }
           }
           //不存在就新建一个entry
            tab[i] = new Entry(key, value);
           int sz = ++size;
           if (!cleanSomeSlots(i, sz) && sz >= threshold)
               rehash();
 }
//ThreadLocal的方法
public T get() {
       Thread t = Thread.currentThread();
       ThreadLocalMap map = getMap(t);
       if (map != null) {
           ThreadLocalMap.Entry e = map.getEntry(this);
           if (e != null) {
               @SuppressWarnings("unchecked")
               T result = (T)e.value;
               return result;
           }
       }
       return setInitialValue();
}
//ThreadLocalMap的方法
private Entry getEntry(ThreadLocal<?> key) {
           int i = key.threadLocalHashCode & (table.length - 1);
           Entry e = table[i];
           if (e != null && e.get() == key)
               return e;
           else
               return getEntryAfterMiss(key, i, e);
}
 
public void remove() {
   ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null) {
        m.remove(this);
     }
}
 
想要存入的ThreadLocal中的数据实际上并没有存到ThreadLocal对象中去,而是以这个ThreadLocal实例作为key存到了当前线程中的一个Map中去了。   ThreadLocal内存泄露问题? 虚拟机栈栈中的ThreadLocal Ref 与堆上的ThreadLocal是强引用的关系,当虚拟机栈中的ThreadLocal Ref 用完之后,准备被回收时,如果entry与堆上的ThreadLocal不是弱引用的话,那么堆上的ThreadLocal对象就不会被回收。而如果是弱引用,当发生Gc时,ThreadLocal用完之后,不存在强引用,而唯一存在一个弱引用,此时ThreadLocal对象就可以被回收了。虽然弱引用可以让堆上的ThreadLocal对象被回收,但是Entry对象还因为线程的存在,有引用可达。ThreadLocal被回收之后,entry里面的key是null, 此时这个entry成为了陈旧项,value对象在线程存在时,仍然会存在,故而仍然有线程泄露的风险。因此就需要某种机制,来对Entry里面的key为null的value进行释放。ThreadLocal采用了线性探测来清除陈旧项(replaceStaleEntry与getEntryAfterMiss方法都干了这个活),从而防止了内存泄漏。当然,我们也可以在用完ThreadLocal之后,手动调用remove方法,去除线程中ThreadLocalMap中的这个entry. 4 ThreadLocal中为什么要加remove方法? 主要是为了及时地将线程中TreadLocalMap中的以这个ThreadLocal为key的entry中的value对象删除。这样就可以避免因为线程长期存在,而ThreadLocal实例用完之后导致的内存泄露问题。 总结: ThreadLocal并不解决线程间共享数据的问题,而是为每个线程提供了一个独立的变量副本。从而避免线程变量的线程安全问题。由于每个线程都有一个执属于自己的ThreadLocalMap,并维护了ThreadLocal与实例之间的映射关系,因此就不存在线程安全以及锁的问题。ThreadLocalMap的entry对ThreadLocal弱引用,避免了ThreadLocal无法被回收的问题。ThreadLocal自身的set、get、remove方法最终会调用expungeStaleEntry、cleanSomeSlots、replaceStaleEntry这三个方法回收键位null的entry对象的value值(实例),以及entry对象本身,从而防止内存泄露。        

标签:ThreadLocalMap,探究,value,ThreadLocal,线程,引用,key,原理
From: https://www.cnblogs.com/mtjb1dd/p/17306651.html

相关文章

  • 15.5二叉排序树原理及建树实战
    #include<stdio.h>#include<stdlib.h>typedefintKeyType;typedefstructBSTNode{KeyTypekey;structBSTNode*lchild,*rchild;}BSTNode,*BiTree;//非递归的创建二叉查找数intBST_Insert(BiTree&T,KeyTypek){BiTreeTreeNew=(BiTree)cal......
  • 扒一扒Nacos、OpenFeign、Ribbon、loadbalancer组件协调工作的原理
    大家好,我是三友~~前几天有个大兄弟问了我一个问题,注册中心要集成SpringCloud,想实现SpringCloud的负载均衡,需要实现哪些接口和规范。既然这个兄弟问到我了,而我又刚好知道,这不得好好写一篇文章来回答这个问题,虽然在后面的聊天中我已经回答过了。接下来本文就以探究一下Nacos、O......
  • AIGC爆火的背后需要掌握的基础原理
    最近AIGC和大模型的大火让视频行业的老板们异常兴奋,以前制作一个视频需要经历文案、配音、画面、出镜等复杂流程,现在应用生成式AI产品自动生成文案脚本,再使用一键生成视频(TTV技术)功能,一天可以完成50条视频产出。人工智能如此降本提效,部分行业的从业者面临职业挑战。AI绘画,AI写作,AI......
  • 深度剖析Redis九种数据结构实现原理,建议收藏
    1.Redis介绍Redis是一个高性能的键值存储系统,支持多种数据结构。包含五种基本类型String(字符串)、Hash(哈希)、List(列表)、Set(集合)、Zset(有序集合),和三种特殊类型Geo(地理位置)、HyperLogLog(基数统计)、Bitmaps(位图)。每种数据结构都是为了解决特定问题而设计的,适用不同的场景。想要......
  • AOP底层原理-Cglib动态代理
      publicclassApp{publicstaticvoidmain(String[]args){UserServiceuserService=UserServiceCglibProxy.createUserServiceCglibProxy(UserServiceImpl.class);userService.save();}}publicclassUserServiceCglibProxy{......
  • SpringBoot线程池和Java线程池的实现原理
    使用默认的线程池方式一:通过@Async注解调用publicclassAsyncTest{@Asyncpublicvoidasync(Stringname)throwsInterruptedException{System.out.println("async"+name+""+Thread.currentThread().getName());Thread.sleep(10......
  • B+树原理详解
    B树与B+树我们今天要介绍的是工作开发中最常接触到的InnoDB存储引擎中的B+树索引。要介绍B+树索引,就不得不提二叉查找树,平衡二叉树和B树这三种数据结构。B+树就是从他们仨演化来的。二叉查找树首先,让我们先看一张图:从图中可以看到,我们为user表(用户信息表)建立了......
  • MySQL主从复制原理剖析与应用实践
    vivo互联网服务器团队-ShangYongxingMySQLReplication(主从复制)是指数据变化可以从一个MySQLServer被复制到另一个或多个MySQLServer上,通过复制的功能,可以在单点服务的基础上扩充数据库的高可用性、可扩展性等。一、背景MySQL在生产环境中被广泛地应用,大量的应用和服务......
  • 15.4折半查找原理及实战
    #include<stdio.h>#include<stdlib.h>#include<time.h>typedefintElemType;typedefstruct{ElemType*elem;//整型指针intTableLen;//存储动态数组里边元素的个数}SSTable;//init进行了随机数生成,折半查找没有使用哨兵voidST_Init(SSTable&ST,i......
  • vue2源码-二、对象响应式原理
    //循环对象进行一次劫持classObserver{constructor(value){this.walk()}walk(data){//重新定义属性Object.keys(data).forEach((key)=>defineReactive(data,key, data[key]))}}//属性劫持//对对象target,定义属性key,值为value//使用Object.definProperty重......