编程文汇

ThreadLocal的正确用法

使用tls的目的

  1. 减少参数传递, 调用用一个静态函数, 就可以获取某个对象.
  2. 作为对象缓存. 这种对象一般都不是线程安全的, 并且创建\销毁的开销较高, 而且每个线程一个就够了. 比如buf, 或者重量级对象.

这两种是使用频率非常高的场景.

初始化时机

使用tls最重要的就是找准初始化时机, 虽然这里讨论的是java中ThreadLocal类, 对于各种语言中的tls都是适用的. 如果初始化方式 或者 时机不对, 会出现无法理解的NPE.

  1. 每个线程在使用tls之前都要初始化.
  2. 不能使用static{}进行初始化, (原因: 参照第一条)
  3. 可以使用双检查法来初始化
  4. 可以在调用业务逻辑之前 初始化, 然后业务逻辑中就可以正常使用tls

更安全的ThreadLocal

简单封装了一下java的ThreadLocal, 可以减少出错几率. 这里才有了延迟加载, 以及双检查法.

/**
 * 可以自动初始化的ThreadLocal
 * zhmt
 * @param <T>
 */
public class EzgThreadLocal<T> {
    private final ThreadLocal<T> tls = new ThreadLocal<>();
    private final Supplier<T> supplier;

    public EzgThreadLocal() {
        this.supplier = null;
    }

    public EzgThreadLocal(Supplier<T> supplier) {
        this.supplier = supplier;
    }

    public void set(T v) {
        tls.set(v);
    }

    public T get() {
        var ret = tls.get();
        if (ret != null) {
            return ret;
        }
        if (supplier == null)
            return null;
        ret = supplier.get();
        tls.set(ret);
        return ret;
    }
}

使用示例:

//声明为静态变量, 如果是普通变量的话, 最好是单例中的field.
private static final EzgThreadLocal<ByteBuf> tranBuf = new EzgThreadLocal<>(() -> Unpooled.buffer(32, 32));

//然后就可以直接使用了.
void test() {
    tranBuf.get().clear();
}