在家办公 💻,不敢出门 🚪,不敢理发 💇
没错,和大家一样,疫情结束后,我们就是镇楼图这个样子。
并且,你是不是也好几天没洗头了呢?
言归正传 ~
本篇文章来源于记忆中的一道题,实现一个 ThreadLocal。
笔者第一次碰到这个题的时候,当时可是非常天真,分分钟写了一下,结果发现是个低配版,请你也继续往下看看,有没有和笔者一样天真过。
在生活中的话,想知道一个东西优点在哪里,该怎么办?
干说,估计大多数人都听不懂。 但是,如果有同类产品作比较,那么优缺就显而易见了。那么同样的,下文中用笔者的低配版MyThreadLocal来看看ThreadLocal有什么优点。
其实当时看到这题的时候,还没仔细看过ThreadLocal的源码,但是脑中还是有几个关键字的,Map,弱引用,于是动手就写了一个版本, 大体实现是这样的:
public class ThreadLocal<T> { private Map<Thread, T> threadValMap = Collections.synchronizedMap(new WeakHashMap<>()); public void set(T value) { threadValMap.put(Thread.currentThread(), value); } public T get() { return threadValMap.get(Thread.currentThread()); } // remove.... } 复制代码
你别说,测试了一下,基本功能没毛病,Thread用完之后消除引用,通知 GC,过一会键值也成功被回收。
但是翻开 ThreadLocal 本身的源码,设计可是大相径庭。
这个低配版和正统版有什么区别?
上文提到的自己实现的ThreadLocal,下文我就一直称呼为MyThreadLocal吧。
MyThreadLocal的实现很简单,借助synchronizedMap
方法实现了一个同步的弱引用HashMap。
那么ThreadLocal内部是怎样实现的,我也画了一张简图:
由于Thread类中设计时就带了一行ThreadLocal.ThreadLocalMap threadLocals = null;
作为属性。
所以,ThreadLocal在赋值的时候,只需要检查Thread类中的ThreadLocalMap是不是空的,空的就创建一个,不空就接着用。
上面两段文字描述的就是MyThreadLocal和ThreadLocal的第一处不同点:
低配版用synchronizedMap做为同步容器,synchronizedMap是同步容器,并不是并发优化容器,源码自然是大量的synchronized
.
而ThreadLocal并没有同步操作,而是操作都是只针对自己当前线程进行操作,是天然的线程封闭。 用DB设计作为比喻的话,可以大概类比为“ 单表加个字段就完事了,你非要再建个关联表,还得注意事务问题 ”。
这样性能就高下立判了,同样我们在单进程编码时也要注意,巧用不可变对象和线程封闭,性能要优于同步和锁。
从上面的图片可以看到,MyThreadLocal的Map直接用Thread作为Key,而ThreadLocal 的Map用的是自己本身。
上文我也提到了,Thread直接作为弱引用的Key,GC也是成功回收了,那么区别在哪?
区别就是只有测试的时候,你才会new Thread();
实际中线程交由线程池管理,线程池内部采用线程复用,那么Thread将一直保持引用,不能被GC掉,可能导致内存泄露。
ThreadLocal倒是也有类似的问题,因为大多数情况我们都会使用static引用ThreadLocal,一直保持强引用。好在ThreadLocal提供了remove方法,只需开发者注意用完后调用即可。
通过当时的笔者写出的低配版MyThreadLocal, 大概也知道了ThreadLocal优点在哪了,同时也感觉有个细节比较有趣:
ThreadLocalMap
内部并没有使用HashMap做辅助,而且直接手写了一个HashMap。
当时看到ThreadLocalMap的代码时,以为大佬们是想秀一下数据结构的功底,实际上,hashMap的诞生要晚于ThreadLocalMap,才是没有用的原因之一。
想想也是这样,Doug Lea和Josh Bloch这种级别的,还用秀基本功吗😄...
原创不易~ 点个赞再走吧!