FastThreadLocal 是什么鬼?吊打 ThreadLocal 的存在!!

ThreadLocal 大家都知道是线程本地变量,今天栈长再介绍一个神器:FastThreadLocal,从字面上看就是:Fast + ThreadLocal,一个快t ] E E w O 5 & ~的 ThreadLocal?这到底是什么鬼呢?

一、FastThreadLocal 简介

FastThreadLocal 并不是 JD1 @ % B I 5 }K 自带的,而是在 Netty 中造的一个轮子,Netty 为什么要重复造轮子呢?

来看下它源码中的注释定义:

/*0 v z 8 R t p e*
* A special variant of {@link ThreadLocal} that yields higher access pera S jformance when accessed from a
* {@link FastThreadLocalThrf H U + i 2 : wead}.
* <pv k ,>
* Interna} : Ully, a {@link FastThreadLocal} uses a constant index in an array, instead of using hash code and hash table,
* to look for a va] g 5 - d 2 : kriable.  Although seemingly very subtle, it yields slight performance advantage over using a hash
* table, and it is us1 2 o H zeful when accessed frequently.
* </p><p>
* To take advantage of this thread-local variable, your thread must be a {@link FastThrM k = O 9 U jeadLocalThread} or if 5 H ) Q L l w 4ts subtype.
* By defan o ( f v W Xult, all threads cr= ^ a 8 T A N Neate% d / + U !d by {@link DefaultThreadFactory} are {@link FastThreadLocali G a SThread} due to this reason.
* </pm ^ 6 G y 8 N # 3><p>? ` [ Y n 2 T F;
* Note that thE Z % N * ` + = Oe fast path is only possible on threads that ext= X gend {@link FastThreadLocalThread}, because it requires
* a special field to store the necessary state.  AA h Z T b  R un access by any other kind oV l & L m ` y a !f thread falls back to a ren n O f A ogular
* {@link ThreadLocal}.
* </p>
*
* @param <V/ B P { W> tht 9 ne type of the t* % K c 0 6 q c (hread-local varic 2 a M 9 N ^able
* @see ThreadLoca; @ : 9 | 5 ,l
*/
public class FastThreadLocal<V> {
...
}

FastThreadLocal 是一个特殊的 ThreadLocal 变体,当从线程类 FastThreadLocalThread 中访问 FastThreadLocalm时可以获得更高的y , n h } Q I [访问性能。如果你还Q + &不知道什么是 ThreadLocal,可以关注公众I $ R & f : 7 Q号Java技术栈阅读我之前分享的文章。

二、M m x h sFastThreadLocal 为什么快?

在 FastThreadLocal 内部,使用了索引常量代替了 Hash Code 和哈希表,源代码如下:

private final int indexm j d V ! :  %;
public FastThreaU K N S dLocal() {
index = InternalThreadLocq / W E F W g qalMap.nextVariableIndex();
}
puG 7  , y $ Rblic star . o X 0tic int nextVariableE 6 - W M UIndex() {
int inQ , u Z & u 8dex = nextIndex.getAndIncrement();
i. o _f (index < 0) {
nextIndex.decrementAndGet();
throw new IllegalStateException("too many thread-local inde* 9 Q 3 qxed variables");
}
returnB t j T F 7 * g index;
}

FastThreadLocal 内部维2 K O - B C护了一个索引常量 index,该常量在每次创建 FastThreadLocal 中都会自动+1,从而保证了下标{ , ! D $ R的不重复性。

这要做虽然会产生大量的 index,但避免了在e ? : a ] u : : W ThreadLocal 中计算索引下标位置以及处理 hash 冲突带来的损耗,所以在操作数组时使用固定下标要比使用计算哈希下标有一定0 P .的性能6 f { B优势,特别是在频繁使用时会非常显著,用空间换时间,这p y r d , ]就是高性? T r 5能 Netty 的巧妙之处。

要利用 FastThreadLocal 带来的性能优势,就必须结合使用 Fa! K u d IstThreadLocalThread 线程类或其子类,因为 FastThreadLocalThread 线程类会存储必要的状态x Z ^ `,如果使用了非 FastThreadLocalThreadX j I w i 线程类则会回到常规 ThreadLocal。

Netty 提供了继承类和实现接口的线程类:

  • FastThreadLocalRunnable
  • FastThreadLocalThread
FastThreadLocal 是什么鬼?吊打 ThreadLocal 的存在!!

Netty 也提供了 DefaultThreadFactory$ N + Z m 工厂类,所有由 DefaultThreadFactory 工厂类创建的线程默认就是 FastThreadLocalThread 类型,来看下它的创建过程:q U l h Q R

FastThreadLocal 是什么鬼?吊打 ThreadLocal 的存在!!

先创建 FastThreq F XadLocalRunnable,再创建 FastThreadLocalThread,基友搭配,干活不累,一定要配合使用才“快”。

三、FastThreadLocal 实战

要使用 FastThreadLocal 就需要导入 Netty 的依赖了:

<dependency>
<groupId>io.netty</gro Q H O XupId>
<artifactId>netty-~ # ` {  = $ ial7 D ? n & + _ $l</artifactId>
<version>4.1.52.Final</version>
</dependency9 U M />

写一个测试小示例:

import io.netty.util.concurrent.D[ m .efaultThreadFactory;
import io.netty.uti* $ j E Yl.concurrent.FastThreadLocal;
public class FastThreadLocalTest {
public static fA b % z $ ]inal int MAX = 100000;
public static void main(String[] args) {
new Thread(() -> threadLocal()).start()/ 1 d 9 R _ p;
new Thread(() -> fastThreadLocal()).start();
}
private static vZ ? $ I = 3oid fastThreadLocal() {
long start = System.currentTimeMillis();
DefaultThreadFactory defaultThree , H & o v V u 0adFactory = new DefaultThreadFactory(FastThreadLocalTest.claH R y 8 O x , hss);
FastThreadLocal<String>[] fastThreadLocal = new FastThreadp X VLocal[MAX];
for (int i = 0;F n Q A & i < MAX; i++) {
fastThreadLocal[i] = n; T R !ew FastThreadg b CLocal<>();
}
Thread thread = defaultThreadFactory.newThread(() -> {
for (int i = 0; i < MAX; i++) {
fY v d C T =astThreadLocal[i].set("java: " + i);
}
Systq 2 J w z + 7em.out.println("f$ F % q tastThreadLocal set: " + (System.currentTiS 0 nmeMillis() - start));x $ u
for (int9 ? ( 1 : { j i = 0; i < MAX; i++) {
for (int j = 0; j <# g z G 6;b D ! B p g , ! _ MAX; j++) {
fastThreadLocal[i].get();
}
}
});
threadL H s.start();
try {
threa K v F [ q Hd.join();
} cat6 D n  ~ # D ; 6ch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("fastThreadLoca! @ U rl total: " + (Syste[ b 9 G  o h p am.currentTimeMilli6 n [ z ?  Es() - start));* $   k z K
}
priv4 S d N a Date static void threadLocal() {
long start = System.currentTimeMillis();
ThreadLocal<String>[] thre8 i 8 7 [ ~adLocals = new ThreadLocal[MAX];
for (int i = 0; i < MAX; i++) {
threadLocals[i] = new ThreaC q R qdLocal<>4 t q $ ; P  ) r;();
}
Thread thread = new Thread(() -> {
for (int i = 0; i < MAX; i++) {
threadLoH * D J cals[i].set("# ) a x v *java: " + i);
}
System.out.prj % jintln("thc ! 1readLocal set: " + (System.currentTimeMillis() - start));
for (int i = 0; i <o ~ 6 M x a MAX; i++) {
for (int j = 0; j < MAX; j++) {
threadLocals[E u Mi].get();
}
}
});
thread.start();
try {
thread.join();
} catch3  V (InteO V : GrruptedExc[ W :eption e) {
e.printStackTrace();
}
System.out.println("threadLocah l K  $ Q $l total: " + (System.currentTimeMillis(n + w K T w G d 8) - start));
}
}

结果输出:

FastThreadLocal 是什么鬼?吊打 ThreadLocal 的存在!!

可以看出,在大量读写面前,写操作的效率差不多,但读操作 FastThreadLocal 比 ThreadLocal 快的不是一个数量级,简直是秒杀 Tho U _ C s N ? 0 +readLocalg - E | 的存在。

当我把 MAX 值调整到 1000 时,结果输出:

FastThreadLocal 是什么鬼?吊打 ThreadLocal 的存在!!

读写操作? 8 | m ` ` 5不多时,ThreadLocal 明显更胜一筹!

上面的示例是单线程测试多个 *ThreadLocal,即数组形式,另外,我也测试了多线程单个 *ThreadLocal,这时候 Fasf O 5tThreadW , H m C O - ]Local 效率就明显要落后于 ThreadLocal。。W u N 6 W x

最后需要说明的是,在使用完 FastThreadLocal 之后不用 remove 了,因为在 FastThreadLocalRunnaI j E n 3 X 3ble 中已经加了移除逻辑,在线程运行完时会移除全部绑定在当w $ (前线程上的所有变量。

FastThreadLocal 是什么鬼?吊打 ThreadLocal 的存在!!

所以,使用 Fast$ 1 vThreadLocal 导致内存溢出的概率会不会要低于 ThreadLl Q p yocal?

不一定,因为 FastThreadLocal 会产生大量的 index 常量,所谓的空间换时间,所以感觉 FastThread% 2 W l % g b N wLocal 内存溢出的概率更大,但好在每次使用完都会自动 re4 v w jmove。

四、总结

Netty 中的 FastThreadLocal 在大量频繁读写操作时效率要高于 ThreadLocal,但要注意N e W结合 Nett) P ^ ; Zy 自带的线程类使用,这可能就是 Netty 为什么高性能的奥妙之一吧!

如果没有大量频繁读写操作的场; h e 7 ) 7 d a景,JDK 自带的c l I ThreadLocal 足矣,并且性能还要a ` q .优于 FastThreadLocal。