Java高级主题:深入解析ThreadLocal的数据结构设计原理及其源代码实现

发布时间:2026/7/28 21:43:00

Java高级主题:深入解析ThreadLocal的数据结构设计原理及其源代码实现 ThreadLocal可以实现完全基于无锁且也不是基于CAS的线程隔离需求,让每个线程可以有自己的本地实例,但如果对ThreadLocal底层设计不了解,那么对甚至无法正确ThreadLocal及其可能出现的内存泄露问题。可以说ThreadLocal的源代码设计也一种非常优秀的可支持“高并发”的实现。1、基本用法:1.1 demo1publicclassThreadLocalDemo{staticclassProducerAextendsThread{@Overridepublicvoidrun(){ThreadLocalStringvar1=newThreadLocal();ThreadLocalStringvar2=newThreadLocal();// 普通用法var1.set("foo A");var2.set("bar A");System.out.println(var1.get());// 输出foo ASystem.out.println(var2.get());// 输出bar A}}staticclassProducerBextendsThread{@Overridepublicvoidrun(){ThreadLocalStringvar1=newThreadLocal();ThreadLocalStringvar2=newThreadLocal();// 普通用法var1.set("foo B");var2.set("bar B");System.out.println(var1.get());// 输出foo BSystem.out.println(var2.get());// 输出bar B}}publicstaticvoidmain(String[]args){newProducerA().start();newProducerB().start();}}这里创建了两个线程,每个线程内部有自己的ThreadLocal变量,线程之间ThreadLocal变量内部的set和get互相独立,互不影响,无需使用锁即可实现了线程安全操作。线程内部的变量使用set方法给定初始值、get方法取值,可以猜测其内部有类似HashMap这样的设计,但是否照搬HashMap数据结构设计呢? 其实不然,在这里,ProducerA内部创建了一个ThreadLocalMap,ProducerB内部也创建了一个ThreadLocalMap,也即每个线程绑定一个自己内部ThreadLocalMap,这里提到的ThreadLocalMap就是提供了set、get方法的底层Map数据结构,所谓的ThreadLocal数据结构分析其实就是特指其内部的ThreadLocalMap的数据结构分析。1.2 demo2publicclassThreadLocalDemo{staticclassHoldCount{intcount;finallongtid=0;}publicstaticvoidmain(String[]args){ThreadLocalHoldCountrh=ThreadLocal.withInitial(HoldCount::new);// 设定rh这个ThreadLocal变量的初始值rh.set(newHoldCount());// 将计数器放在rh中缓存HoldCounth=rh.get();System.out.println(h.count);// 这里输出的rh初始值,也即HoldCount的count属性初始值:0。for(inti=0;i10;i++){h.count++;}System.out.println(h.count);HoldCountnewh=rh.get();// 更新缓存计数器后,再从ThreadLocal重新读取System.out.println(newh.count);// 可以读取新的计数值// 在rh这个ThreadLocal里面的Map结构中移除HoldCount实例对象rh.remove();System.out.println(rh.get());// 此时rh里面Map已经不存在HoldCount对象,因此这里返回NUll}}在demo2中,给出了使用ThreadLocal后需要及时删除其实例对象的情况,这部分原因将在文章后面给出深入分析。2、ThreadLocal内部数据结构简析可以看到set方法是由内部ThreadLocalMap实现的set方法,既然是个“Map”,那么当然可以猜测是否跟HashMap的数据结构:数据+链表+红黑树类似呢?publicvoidset(Tvalue){Threadt=Thread.currentThread();ThreadLocalMapmap=getMap(t);if(map!=null)map.set(this,value);elsecreateMap(t,value);}其实ThreadLocalMap的数据结构没有HashMap数据结构复杂,ThreadLocalMap底层仅有一个table数组,这里,也许你会好奇:HashMap为了解决hash冲突,在数组的桶位上加入一条单向链表,冲突的entry自然会放入到此链表中(或者红黑树),那么问题来了,ThreadLocalMap底层仅有一个table数组,它是如何解决hash冲突?以下正式其设计原理之一,这里的数组给出最简单的情况,不包括“stale entry(无效entry)”的情况,以便让读者快速理解ThreadLocalMap设计原理:可以看到图中所说的“解决冲突的方式:从i=3开始向后遍历出首个空slot,也即i=5,将keyC放入此空slot即可”的逻辑被称为“线性探测法”,所谓的“线性”就是o(n)复杂度的遍历操作,所谓的“探测”就是不断向后“探测、寻找”,直到找到首个空slot位置。以上内容为ThreadLocalMap的放入new Entry的简单情况,如果有理解HashMap源代码设计的读者应该可以猜到其他重要设计:例如,当数组容量不够时,如何扩容,也即rehash(注意ThreadLocalMap里面的resize和rehash不是同一个逻辑),再例如ThreadLocalMap里面已经存在的entry,如果它的key已经变成无效(stale),那么如何该清理,或者说在set和get的线性探测过程中遇到有stale entry时,该如何清理?这些问题将在后面逐个深入探讨。3、ThreadLocalMap基本成员变量的说明staticclassThreadLocalMap{/** * The entries in this hash map extend WeakReference, using * its main ref field as the key (which is always a * ThreadLocal object). Note that null keys (i.e. entry.get() * == null) mean that the key is no longer referenced, so the * entry can be expunged from table. Such entries are referred to * as "stale entries" in the code that follows. */// ThreadLocalMap底层数组存放的WeakReference类型的entry,使用弱引用类型是为了能够高效GC,避免内存泄露,文章后面给出此设计的讨论staticclassEntryextendsWeakReferenceThreadLocal?{/** The value associated with this ThreadLocal. */Objectvalue;/* entry的key就是ThreadLocal对象,例如一个线程内部有10个ThreadLocal变量,那么此线程内部的ThreadLocalMap将存放这10个entry,这里的value就是ThreadLocal变量的“值”。 例如demo1中: ThreadLocalString var1=new ThreadLocal(); var1.set("foo A") 那么entry的key就是这个名称为var1的ThreadLocal对象,value就是字符串“foo A” */Entry(ThreadLocal?k,Objectv){super(k);value=v;}}/** * The initial capacity -- MUST be a power of two. */// 数组的初始容量16privatestaticfinalintINITIAL_CAPACITY=16;/** * The table, resized as necessary. * table.length MUST always be a power of two. *///ThreadLocalMap的底层数组,这里也采用2的次方,原因在HashMap的源代码讨论已经给出深入的解析,这里不再累赘。privateEntry[]table;/** * The number of entries in the table. */// 数据含有entry的个数,注意即使entry的key处于stale状态,它也算一个entryprivateintsize=0;/** * The next size value at which to resize. */privateintthreshold;// Default to 0/** * Set the resize thr

相关新闻