ThreadLocal 大家都知道是线程本地变量,今天栈长再介绍一个神器:FastThreadLocal,从字面上看就是:Fast + ThreadLocal,一个快的 ThreadLocal?这到底是什么鬼呢?
一、FastThreadLocal 简介FastThreadLocal 并不是 JDK 自带的,而是在 Netty 中造的一个轮子,Netty 为什么要重复造轮子呢?
来看下它源码中的注释定义:
/** * A special variant of {@link ThreadLocal} that yields higher access performance when accessed from a * {@link FastThreadLocalThread}. * <p> * Internally, a {@link FastThreadLocal} uses a constant index in an array, instead of using hash code and hash table, * to look for a variable. Although seemingly very subtle, it yields slight performance advantage over using a hash * table, and it is useful when accessed frequently. * </p><p> * To take advantage of this thread-local variable, your thread must be a {@link FastThreadLocalThread} or its subtype. * By default, all threads created by {@link DefaultThreadFactory} are {@link FastThreadLocalThread} due to this reason. * </p><p> * Note that the fast path is only possible on threads that extend {@link FastThreadLocalThread}, because it requires * a special field to store the necessary state. An access by any other kind of thread falls back to a regular * {@link ThreadLocal}. * </p> * * @param <V> the type of the thread-local variable * @see ThreadLocal */ public class FastThreadLocal<V> { ... }FastThreadLocal 是一个特殊的 ThreadLocal 变体,当从线程类 FastThreadLocalThread 中访问 FastThreadLocalm时可以获得更高的访问性能。如果你还不知道什么是 ThreadLocal,可以关注公众号Java技术栈阅读我之前分享的文章。
二、FastThreadLocal 为什么快?在 FastThreadLocal 内部,使用了索引常量代替了 Hash Code 和哈希表,源代码如下:
private final int index; public FastThreadLocal() { index = InternalThreadLocalMap.nextVariableIndex(); } public static int nextVariableIndex() { int index = nextIndex.getAndIncrement(); if (index < 0) { nextIndex.decrementAndGet(); throw new IllegalStateException("too many thread-local indexed variables"); } return index; }FastThreadLocal 内部维护了一个索引常量 index,该常量在每次创建 FastThreadLocal 中都会自动+1,从而保证了下标的不重复性。
这要做虽然会产生大量的 index,但避免了在 ThreadLocal 中计算索引下标位置以及处理 hash 冲突带来的损耗,所以在操作数组时使用固定下标要比使用计算哈希下标有一定的性能优势,特别是在频繁使用时会非常显著,用空间换时间,这就是高性能 Netty 的巧妙之处。
要利用 FastThreadLocal 带来的性能优势,就必须结合使用 FastThreadLocalThread 线程类或其子类,因为 FastThreadLocalThread 线程类会存储必要的状态,如果使用了非 FastThreadLocalThread 线程类则会回到常规 ThreadLocal。
Netty 提供了继承类和实现接口的线程类:
FastThreadLocalRunnable
FastThreadLocalThread
Netty 也提供了 DefaultThreadFactory 工厂类,所有由 DefaultThreadFactory 工厂类创建的线程默认就是 FastThreadLocalThread 类型,来看下它的创建过程:
先创建 FastThreadLocalRunnable,再创建 FastThreadLocalThread,基友搭配,干活不累,一定要配合使用才“快”。
三、FastThreadLocal 实战要使用 FastThreadLocal 就需要导入 Netty 的依赖了:
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.52.Final</version> </dependency>写一个测试小示例:
import io.netty.util.concurrent.DefaultThreadFactory; import io.netty.util.concurrent.FastThreadLocal; public class FastThreadLocalTest { public static final int MAX = 100000; public static void main(String[] args) { new Thread(() -> threadLocal()).start(); new Thread(() -> fastThreadLocal()).start(); } private static void fastThreadLocal() { long start = System.currentTimeMillis(); DefaultThreadFactory defaultThreadFactory = new DefaultThreadFactory(FastThreadLocalTest.class); FastThreadLocal<String>[] fastThreadLocal = new FastThreadLocal[MAX]; for (int i = 0; i < MAX; i++) { fastThreadLocal[i] = new FastThreadLocal<>(); } Thread thread = defaultThreadFactory.newThread(() -> { for (int i = 0; i < MAX; i++) { fastThreadLocal[i].set("java: " + i); } System.out.println("fastThreadLocal set: " + (System.currentTimeMillis() - start)); for (int i = 0; i < MAX; i++) { for (int j = 0; j < MAX; j++) { fastThreadLocal[i].get(); } } }); thread.start(); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("fastThreadLocal total: " + (System.currentTimeMillis() - start)); } private static void threadLocal() { long start = System.currentTimeMillis(); ThreadLocal<String>[] threadLocals = new ThreadLocal[MAX]; for (int i = 0; i < MAX; i++) { threadLocals[i] = new ThreadLocal<>(); } Thread thread = new Thread(() -> { for (int i = 0; i < MAX; i++) { threadLocals[i].set("java: " + i); } System.out.println("threadLocal set: " + (System.currentTimeMillis() - start)); for (int i = 0; i < MAX; i++) { for (int j = 0; j < MAX; j++) { threadLocals[i].get(); } } }); thread.start(); try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("threadLocal total: " + (System.currentTimeMillis() - start)); } }结果输出: