我是「 kangarooking(袋鼠帝) 」,不是弟弟的弟。真心给大家分享经验和技术干货,面试次数100+。面试经验绝对丰富,也当过面试官,内容绝对用心可靠。点赞在看,养成习惯。关注me,每天进步亿点点 ❗ ❗ ❗
前言
文章的开头先聊一下为什么最近很久都没有更新了,我们公司需要考一个证,具体什么证就不说了。但是因为疫情,这个考试一直在推迟,之前本来二月份有一次考试的,但是临考之前两天又通知延期了(延期到5月底)。三月份接到公司说考不过就要求离职的通知,压力也比较大,一直在准备考试的内容,也就是下一次考试是我最后的机会。时间到了五月初又被通知延期了,目前还不知道下一次考试时间是什么时候,还是有点焦虑的。趁着这段时间不用备考,我赶紧捡起写文章这个事情。
再说一个感悟吧,最近接到公司的一个任务,参与公司科技宣传视频的拍摄,分到一个组的小伙伴有一半都是国外毕业回来的应届生。感慨有钱确实不一样,我出来打拼了几年进的公司,人家毕业就可以进了,起点确实高(当然不是说所有的国外读书的小伙伴都是靠有钱哈,不会以偏概全。当然有条件的小伙伴真的可以出国留学一下,读万卷书,不如行万里路嘛)穷人真的只有靠不断的努力和正确的选择才能改变命运。当然自己还是要保持自信!对自己有正确的认知,知道自己需要什么,接下来要做什么,规划好自己的人生。跟自己比较,少和别人去比较,如果今天的自己比昨天又更进了一步,这何尝不是一种成功呢。
好了我一个理科生,自从上大学之后就再也没有触碰过文学语文这方面了。就不扯太多了,毕竟文笔也不好。只求大家看得懂就行~~
阅读本篇文章前需要对ThreadLocal有一定的了解
FastThreadLocal相关类:
FastThreadLocalRunnable,FastThreadLocal,InternalThreadLocalMap,FastThreadLocalThread;
与ThreadLocal类比:
| FastThreadLocal | ThreadLocal | 描述 |
|---|---|---|
| FastThreadLocalRunnable | Runnable | 一个可执行任务 |
| InternalThreadLocalMap | ThreadLocalMap | 线程的成员变量 |
| FastThreadLocalThread | Thread | 线程 |
实现方式的不同
ThreadLocal的结构:
ThreadLocal是在Thread类中增加一个成员变量ThreadLocalMap(是一个key,value存储的结构)key是某个ThreadLocal对象,value就是该ThreadLocal对应的值:
ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();
stringThreadLocal.set("kangarooKing");
String s = stringThreadLocal.get();
/**************************************/
public T get() {
Thread t = Thread.currentThread();
//获取当前线程中的ThreadLocalMap变量
ThreadLocalMap map = getMap(t);
if (map != null) {
//通过this也就是当前的ThreadLocal对象为key获取value
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
ThreadLocalMap的数据结构和HashMap有些类似,好奇的小伙伴自行学习,本篇主要讲FashThreadLocal
FastThreadLocal的结构:
InternalThreadLocalMap对标ThreadLocalMap是FastThreadLocalThread(对标Thread)的成员变量,这个map它内部结构其实就是一个Object[]数组,数组里面的值就是value,那么怎么获取value呢,肯定只能是下标了。InternalThreadLocalMap中有一个:
private static final AtomicInteger nextIndex = new AtomicInteger();
它就是用来生成下标的,生成的下标赋值给FastThreadLocalThread的成员变量index。这样在FastThreadLocalThread.get()的时候就可以根据自身的index下标从当前线程的InternalThreadLocalMap的Object[]数组中获取value了。index是在ThreadLocal的无参构造方法里面就赋值了,也就是说new一个新的FastThreadLocal对象就会自动获取到对应的index。从static final AtomicInteger可以看出nextIndex是一个静态变量,在程序里面每新建一个FastThreadLocal对象该值就会+1。
比如:当前有线程1,线程2,线程1创建了一个FastThreadLocal对象index=1,线程2也创建了一个FastThreadLocal对象index=2,这时到线程3创建了,线程3在调用FastThreadLocal的无参构造方法时获取到的nextIndex值为3。
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
结论:FastThreadLocal对象通过创建时获取的index下标,在set时通过该下标set进当前线程的InternalThreadLocalMap的Object[]中,get的时候也通过下标从当前线程的InternalThreadLocalMap的Object[]中获取对应的value。
优点:
用于debug调试的代码:
import io.netty.util.concurrent.FastThreadLocal;
import io.netty.util.concurrent.FastThreadLocalThread;
/**
* netty自定义的FastThreadLocal就是通过空间换时间的方式
* 一个FastThreadLocalThread对应一个InternalThreadLocalMap,内部indexedVariables是一个Object[]数组来存储值
* 一个FastThreadLocal对象代表一个下标,下标从1一次递增。
* get的时候通过当前FastThreadLocal对象的index下标从,InternalThreadLocalMap的indexedVariables获取值,时间复杂度O1
*/
public class FastThreadLocalClient {
public static void main(String[] args) {
FastThreadLocal<Integer> fastThreadLocal = new FastThreadLocal<>();
FastThreadLocal<Integer> fastThreadLocal1 = new FastThreadLocal<>();
FastThreadLocal<Integer> fastThreadLocal2 = new FastThreadLocal<>();
FastThreadLocal<Integer> fastThreadLocal3 = new FastThreadLocal<>();
FastThreadLocal<Integer> fastThreadLocal4 = new FastThreadLocal<>();
new FastThreadLocalThread(() -> {
fastThreadLocal.set(5);
fastThreadLocal1.set(6);
fastThreadLocal2.set(7);
fastThreadLocal3.set(8);
fastThreadLocal4.set(9);
fastThreadLocal4.remove();
Integer integer1 = fastThreadLocal4.get();
System.out.println(integer1);
FastThreadLocal<Integer> fastThreadLocal6 = new FastThreadLocal<>();
System.out.println("thread:" + Thread.currentThread().getName() + " 放入i=" + 5);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
Integer integer = fastThreadLocal.get();
System.out.println("thread:" + Thread.currentThread().getName() + " 取出i=" + integer);
}).start();
FastThreadLocal<Integer> fastThreadLocal9 = new FastThreadLocal<>();
}
}
FastThreadLocal代码分析
1.get方法
FastThreadLocal内部代码
private final int index;
public FastThreadLocal() {
index = InternalThreadLocalMap.nextVariableIndex();
}
public final V get() {
//从当前线程中获取其成员变量InternalThreadLocalMap
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
//通过下标获取到值
Object v = threadLocalMap.indexedVariable(index);
if (v != InternalThreadLocalMap.UNSET) {
return (V) v;
}
return initialize(threadLocalMap);
}
InternalThreadLocalMap内
public static InternalThreadLocalMap get() {
//获取当前线程
Thread thread = Thread.currentThread();
if (thread instanceof FastThreadLocalThread) {
//如果当前线程属于FastThreadLocalThread,就走快速get逻辑
return fastGet((FastThreadLocalThread) thread);
} else {
//如果当前线程属于普通线程就走jdk ThreadLocal的逻辑
return slowGet();
}
}
//这就是InternalThreadLocalMap中存储value的数组
/** Used by {@link FastThreadLocal} */
private Object[] indexedVariables;
//根据下标获取数组中的value
public Object indexedVariable(int index) {
Object[] lookup = indexedVariables;
return index < lookup.length? lookup[index] : UNSET;
}
2.set方法
FastThreadLocal方法
//FastThreadLocal中用于获取value的下标
private final int index;
public FastThreadLocal() {
//在创建对象的时候为index赋值
index = InternalThreadLocalMap.nextVariableIndex();
}
public final void set(V value) {
//set前先判断value不是一个空节点值
if (value != InternalThreadLocalMap.UNSET) {
//获取当前线程的InternalThreadLocalMap
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.get();
//set value
setKnownNotUnset(threadLocalMap, value);
} else {
remove();
}
}
private void setKnownNotUnset(InternalThreadLocalMap threadLocalMap, V value) {
//将value放到对应的index下标
if (threadLocalMap.setIndexedVariable(index, value)) {
//上面的方法返回true执行该方法
//该方法是用来将所有当前线程使用到的FastThreadLocal对象存储到
//InternalThreadLocalMap的Object[]的0下标位置,也就是0下标位置的value是一个集合
addToVariablesToRemove(threadLocalMap, this);
}
}
InternalThreadLocalMap内部方法
//注释的意思当且仅当一个新的FastThreadLocal被创建时才会返回true
/**
* @return {@code true} if and only if a new thread-local variable has been created
*/
public boolean setIndexedVariable(int index, Object value) {
Object[] lookup = indexedVariables;
if (index < lookup.length) {
Object oldValue = lookup[index];
lookup[index] = value;
return oldValue == UNSET;
} else {
expandIndexedVariableTableAndSet(index, value);
return true;
}
}
为什么用oldValue == UNSET来判断当前FastThreadLocal对象第一次被当前线程使用呢,因为InternalThreadLocalMap在初始化Object[]的时候直接将Object[]填满了UNSET对象。
private static final int INDEXED_VARIABLE_TABLE_INITIAL_SIZE = 32;
public static final Object UNSET = new Object();
private InternalThreadLocalMap() {
indexedVariables = newIndexedVariableTable();
}
private static Object[] newIndexedVariableTable() {
Object[] array = new Object[INDEXED_VARIABLE_TABLE_INITIAL_SIZE];
Arrays.fill(array, UNSET);
return array;
}
小结:FastThreadLocal的set方法将value根据index放入当前线程InternalThreadLocalMap的Object[]相应位置,并且会判断当前FastThreadLocal是否第一次在该线程被使用,如果是的话就将该FastThreadLocal对象加入到Object[] 0下标位置的集合中。
注意:FastThreadLocal中的index是从1开始递增的。而0下标是在程序启动的时候就已经优先分配了(这个会在后面的防止内存泄漏讲解中提到)。
InternalThreadLocalMap中对FastThreadLocal index的计算,就是递增
private static final AtomicInteger nextIndex = new AtomicInteger();
public static int nextVariableIndex() {
int index = nextIndex.getAndIncrement();
if (index >= ARRAY_LIST_CAPACITY_MAX_SIZE || index < 0) {
nextIndex.set(ARRAY_LIST_CAPACITY_MAX_SIZE);
throw new IllegalStateException("too many thread-local indexed variables");
}
return index;
}
3.扩容方式
其实也就是类似ArrayList扩容,先创建一个更大的新数组,然后将老数组copy进新数组。
private void expandIndexedVariableTableAndSet(int index, Object value) {
Object[] oldArray = indexedVariables;
final int oldCapacity = oldArray.length;
int newCapacity;
if (index < ARRAY_LIST_CAPACITY_EXPAND_THRESHOLD) {
newCapacity = index;
newCapacity |= newCapacity >>> 1;
newCapacity |= newCapacity >>> 2;
newCapacity |= newCapacity >>> 4;
newCapacity |= newCapacity >>> 8;
newCapacity |= newCapacity >>> 16;
newCapacity ++;
} else {
newCapacity = ARRAY_LIST_CAPACITY_MAX_SIZE;
}
Object[] newArray = Arrays.copyOf(oldArray, newCapacity);
Arrays.fill(newArray, oldCapacity, newArray.length, UNSET);
newArray[index] = value;
indexedVariables = newArray;
}
4.remove方法
FastThreadLocal方法
public final void remove(InternalThreadLocalMap threadLocalMap) {
if (threadLocalMap == null) {
return;
}
Object v = threadLocalMap.removeIndexedVariable(index);
removeFromVariablesToRemove(threadLocalMap, this);
if (v != InternalThreadLocalMap.UNSET) {
try {
onRemoval((V) v);
} catch (Exception e) {
PlatformDependent.throwException(e);
}
}
}
InternalThreadLocalMap内部方法
public Object removeIndexedVariable(int index) {
Object[] lookup = indexedVariables;
if (index < lookup.length) {
Object v = lookup[index];
lookup[index] = UNSET;
return v;
} else {
return UNSET;
}
}
5.避免内存泄漏的方式
还记得set中的这个方法吗
该方法用于将第一次在当前线程中被使用的FastThreadLocal对象放入Object[] 0下标的集合中,用于在当前线程执行完之后方便将所有该线程使用到的FastThreadLocal清理掉。
下面的代码属于FastThreadLocal类中。
//这里就是分配Object[] 0下标的地方
//因为是静态变量,所以在类加载的初始化阶段就会被执行且赋值,所以这里最先执行获取了0下标。
private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();
//所以index就只能从1获取。
private final int index;
@SuppressWarnings("unchecked")
private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
//从Object[]的0下标获取集合
Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
Set<FastThreadLocal<?>> variablesToRemove;
if (v == InternalThreadLocalMap.UNSET || v == null) {
//如果0下标还没有集合对象就新建一个
variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
//将新建的集合对象赋值到0下标
threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
} else {
variablesToRemove = (Set<FastThreadLocal<?>>) v;
}
//往集合里面插入当前FastThreadLocal对象,0下标已经持有该集合的引用
//所以先赋值集合到0下标,后插值到集合,是可以的
variablesToRemove.add(variable);
}
public static void removeAll() {
InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
if (threadLocalMap == null) {
return;
}
try {
Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
if (v != null && v != InternalThreadLocalMap.UNSET) {
@SuppressWarnings("unchecked")
Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
FastThreadLocal<?>[] variablesToRemoveArray =
variablesToRemove.toArray(new FastThreadLocal[0]);
for (FastThreadLocal<?> tlv: variablesToRemoveArray) {
tlv.remove(threadLocalMap);
}
}
} finally {
InternalThreadLocalMap.remove();
}
}
InternalThreadLocalMap.remove()
public static void remove() {
Thread thread = Thread.currentThread();
if (thread instanceof FastThreadLocalThread) {
((FastThreadLocalThread) thread).setThreadLocalMap(null);
} else {
slowThreadLocalMap.remove();
}
}
最后我们可以看到在FastThreadLocalThread线程执行完run方法之后,一定会执行FastThreadLocal.removeAll();方法,最后解除相关引用,避免垃圾回收时还有强引用而回收失败导致内存泄漏的情况。
最后再来对比一下ThreadLocal:
| FastThreadLocal | 描述 | ThreadLocal | 描述 |
|---|---|---|---|
| FastThreadLocalRunnable | 一个可执行任务 | Runnable | 一个可执行任务 |
| InternalThreadLocalMap | 线程的成员变量 | ThreadLocalMap | 线程的成员变量 |
| FastThreadLocalThread | 线程 | Thread | 线程 |
优缺点总结
FastThreadLocal比ThreadLocal快在哪:
-
采用数组和下标获取的方式来进行数据的存取,获取数据以及存放数据都不需要计算
hash值,时间复杂度O1。 -
因为
FastThreadLocal对象在创建的时候就已经确定了index,所以后续的不管是get还是set或是remove都是基于确定的index去操作。时间复杂度都是O1。相当的快。 -
还有就是在内存泄漏的处理上比
ThreadLocal更优雅,ThreadLocal虽然把key设置为了弱引用,但是存在value回收不掉的情况,就造成了内存泄漏,当线程越来越多,堆积的未回收的value越多的时候就会导致内存溢出。FastThreadLocal就相当于没有key,硬要说有key就是index,然后value在线程执行完之后的finally代码块里面被清理掉。所以FastThreadLocal在内存泄漏的处理上不需要开发者做多余的考虑。虽然ThreadLocal会在set,get等方法里面将空key对应的value清理掉,但还是没有FastThreadLocal的设计来的智能。
总结:
所以说软件的设计是很重要的,同样的功能不同的设计带来的效率和结果是会不一样的。前期的设计很大程度上决定了最后产出软件的质量。然后说一下使用场景:首先我认为ThreadLocal能使用的场景FastThreadLocal都适用,其次可以使用在会产生大量ThreadLocal对象的系统,并且该系统对性能要求较高,因为当FastThreadLocal对象很多的时候获取某一个的value只需要用下标直接定位,速度非常快。或者对于系统稳定性要求比较高的系统,因为FastThreadLocal不需要开发者太关注内存泄漏的问题。
学而不思则罔,so
思考:
1.为什么不把Object[]对象直接放在FastThreadLocal中呢?
2.FastThreadLocal有什么缺点呢?
欢迎大家在评论区讨论~~~
微信公众号「 袋鼠先生的客栈 」,有问题评论区见。如果你觉得我的分享对你有帮助,或者觉得我有两把刷子,就支持一下我这个初出茅庐的writer吧,不需要money,三连,三连,三连就是我最大的动力~,接下来会持续分享一些干货~。点赞👍 关注❤️ 分享👥