ThreadLocal的思考

ThreadLocal的思考

ThreadLocal

ThreadLocal是线程本地变量,每个线程往这个ThreadLocal中读写是线程隔离,互相之间不会影响的。它提供了一种将可变数据通过每个线程有自己的独立副本从而实现线程封闭的机制。

Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap。ThreadLocalMap参照HashMap的实现,ThreadLocalMap的key为ThreadLocal,value为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。每个线程在往某个ThreadLocal里塞值的时候,都会往自己的ThreadLocalMap里存,读也是以某个ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。

	/* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap

ThreadLocalMap是ThreadLocal的静态内部类。ThreadLocalMap提供了一种为ThreadLocal定制的高效实现,并且自带一种基于弱引用的垃圾清理机制。
ThreadLocalMap是类似HashMap实现的另一种map实现,有自己的key和value,key为ThreadLocal,value为代码中放入的值。和HashMap一样有一个table数据,有get和set方法,在key的hash冲突时往下寻找对应对象槽位。
ThreadLocalMap里的节点Entry不同于HashMap里面的Entry,继承弱引用,是如下定义的。

		static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

为什么用弱引用

弱引用是Java中四档引用的第三档,比软引用更加弱一些,如果一个对象没有强引用链可达,那么一般活不过下一次GC。当某个ThreadLocal已经没有强引用可达,则随着它被垃圾回收,在ThreadLocalMap里对应的Entry的键值会失效,这为ThreadLocalMap本身的垃圾清理提供了便利。

InheritableThreadLocal

InheritableThreadLocal继承ThreadLocal,ThreadLocal在线程内实现一个局部变量,可以在线程的任何地方来访问,能够减少参数的传递,InheritableThreadLocal在子线程和父线程之间共享线程实例本地变量,也同样是为了减少参数的传递。
Thread类中还有一个类型为ThreadLocal.ThreadLocalMap的变量inheritableThreadLocals:

	/*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

ThreadLocal使用的是Thread的实例变量threadLocals;InheritableThreadLocal使用的是Thread的实例变量inheritableThreadLocals。这两个变量类型都为ThreadLocal.ThreadLocalMap,用途不一样。

InheritableThreadLocal的实现如下:

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
    /**
     * Computes the child's initial value for this inheritable thread-local
     * variable as a function of the parent's value at the time the child
     * thread is created.  This method is called from within the parent
     * thread before the child is started.
     * <p>
     * This method merely returns its input argument, and should be overridden
     * if a different behavior is desired.
     *
     * @param parentValue the parent thread's value
     * @return the child thread's initial value
     */
    protected T childValue(T parentValue) {
        return parentValue;
    }

    /**
     * Get the map associated with a ThreadLocal.
     *
     * @param t the current thread
     */
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }

    /**
     * Create the map associated with a ThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the table.
     */
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

可以看出,InheritableThreadLocal只是将Thread中的threadLocals替换成inheritableThreadLocals。

InheritableThreadLocal如何实现父子线程间共享

线程在初始化时,会将父线程中的ThreadLocalMap复制到子线程中:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc) {
        //此处代码无关,忽略
        ...
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }
	static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
        return new ThreadLocalMap(parentMap);
    }

将父线程中inheritableThreadLocals变量的ThreadLocalMap中的Entry复制到子线程中:

	private ThreadLocalMap(ThreadLocalMap parentMap) {
            Entry[] parentTable = parentMap.table;
            int len = parentTable.length;
            setThreshold(len);
            table = new Entry[len];

			//将父线程中inheritableThreadLocals变量的ThreadLocalMap中的Entry复制到子线程中
            for (int j = 0; j < len; j++) {
                Entry e = parentTable[j];
                if (e != null) {
                    @SuppressWarnings("unchecked")
                    ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
                    if (key != null) {
                        Object value = key.childValue(e.value);
                        Entry c = new Entry(key, value);
                        int h = key.threadLocalHashCode & (len - 1);
                        while (table[h] != null)
                            h = nextIndex(h, len);
                        table[h] = c;
                        size++;
                    }
                }
            }
        }

Transmittable ThreadLocal(TTL)

JDK的InheritableThreadLocal类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的.这时,ThreadLocal和基于父子线程关系的InheritableThreadLocal值传递已经没有了意义。应用实际上需要的是把任务提交给线程池时的线程的ThreadLocal值传递给实际执行的线程。

TransmittableThreadLocal(TTL) 是Alibaba开源的、用于解决 “在使用线程池等会缓存线程的组件情况下传递ThreadLocal” 问题的 InheritableThreadLocal 扩展.

TransmittableThreadLocal.Transmitter提供了所有TTL值的抓取、回放和恢复方法(即CRR操作):
1.capture方法:抓取线程(线程A)的所有TTL值。
2.replay方法:在另一个线程(线程B)中,回放在capture方法中抓取的TTL值(回放的过程也就是把第一步抓取的线程实例本地变量设置到当前线程的实例本地变量中),并返回 回放前TTL值的备份
3.restore方法:恢复线程B执行replay方法之前的TTL值(即备份,把第二步返回的回放前TTL值重新设置回池化中线程的实例本地变量)

整个过程的完整时序图
在这里插入图片描述代码解析:
capture:

private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
    this.capturedRef = new AtomicReference<Object>(capture());
    this.runnable = runnable;
    this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}
public static Object capture() {
	return new Snapshot(captureTtlValues(), captureThreadLocalValues());
}
private static WeakHashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
	WeakHashMap<TransmittableThreadLocal<Object>, Object> ttl2Value = new WeakHashMap<TransmittableThreadLocal<Object>, Object>();
    for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
        ttl2Value.put(threadLocal, threadLocal.copyValue());
    }
    return ttl2Value;
}
private static WeakHashMap<ThreadLocal<Object>, Object> captureThreadLocalValues() {
    final WeakHashMap<ThreadLocal<Object>, Object> threadLocal2Value = new WeakHashMap<ThreadLocal<Object>, Object>();
    for (Map.Entry<ThreadLocal<Object>, TtlCopier<Object>> entry : threadLocalHolder.entrySet()) {
        final ThreadLocal<Object> threadLocal = entry.getKey();
        final TtlCopier<Object> copier = entry.getValue();

        threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get()));
    }
    return threadLocal2Value;
}

repaly:

public void run() {
    Object captured = capturedRef.get();
    if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {
        throw new IllegalStateException("TTL value reference is released after run!");
    }

    Object backup = replay(captured);
    try {
        runnable.run();
    } finally {
        restore(backup);
    }
}
@NonNull
public static Object replay(@NonNull Object captured) {
    final Snapshot capturedSnapshot = (Snapshot) captured;
    return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value));
}
@NonNull
private static WeakHashMap<TransmittableThreadLocal<Object>, Object> replayTtlValues(@NonNull WeakHashMap<TransmittableThreadLocal<Object>, Object> captured) {
    WeakHashMap<TransmittableThreadLocal<Object>, Object> backup = new WeakHashMap<TransmittableThreadLocal<Object>, Object>();

    for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
        TransmittableThreadLocal<Object> threadLocal = iterator.next();

        // backup
        backup.put(threadLocal, threadLocal.get());

        // clear the TTL values that is not in captured
        // avoid the extra TTL values after replay when run task
        if (!captured.containsKey(threadLocal)) {
            iterator.remove();
            threadLocal.superRemove();
        }
    }

    // set TTL values to captured
    setTtlValuesTo(captured);

    // call beforeExecute callback
    doExecuteCallback(true);

    return backup;
}
private static WeakHashMap<ThreadLocal<Object>, Object> replayThreadLocalValues(@NonNull WeakHashMap<ThreadLocal<Object>, Object> captured) {
    final WeakHashMap<ThreadLocal<Object>, Object> backup = new WeakHashMap<ThreadLocal<Object>, Object>();

    for (Map.Entry<ThreadLocal<Object>, Object> entry : captured.entrySet()) {
        final ThreadLocal<Object> threadLocal = entry.getKey();
        backup.put(threadLocal, threadLocal.get());

        final Object value = entry.getValue();
        if (value == threadLocalClearMark) threadLocal.remove();
    	    else threadLocal.set(value);
    }

   	return backup;
}

restore:

public static void restore(@NonNull Object backup) {
    final Snapshot backupSnapshot = (Snapshot) backup;
    restoreTtlValues(backupSnapshot.ttl2Value);
    restoreThreadLocalValues(backupSnapshot.threadLocal2Value);
}
private static void restoreTtlValues(@NonNull WeakHashMap<TransmittableThreadLocal<Object>, Object> backup) {
    // call afterExecute callback
    doExecuteCallback(false);

    for (final Iterator<TransmittableThreadLocal<Object>> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {
        TransmittableThreadLocal<Object> threadLocal = iterator.next();

        // clear the TTL values that is not in backup
        // avoid the extra TTL values after restore
        if (!backup.containsKey(threadLocal)) {
            iterator.remove();
            threadLocal.superRemove();
        }
    }

    // restore TTL values
    setTtlValuesTo(backup);
}
private static void restoreThreadLocalValues(@NonNull WeakHashMap<ThreadLocal<Object>, Object> backup) {
    for (Map.Entry<ThreadLocal<Object>, Object> entry : backup.entrySet()) {
        final ThreadLocal<Object> threadLocal = entry.getKey();
        threadLocal.set(entry.getValue());
    }
}

注意: 在capture方法中捕获的Snapshot类如下:

		private static class Snapshot {
            final WeakHashMap<TransmittableThreadLocal<Object>, Object> ttl2Value;
            final WeakHashMap<ThreadLocal<Object>, Object> threadLocal2Value;

            private Snapshot(WeakHashMap<TransmittableThreadLocal<Object>, Object> ttl2Value, WeakHashMap<ThreadLocal<Object>, Object> threadLocal2Value) {
                this.ttl2Value = ttl2Value;
                this.threadLocal2Value = threadLocal2Value;
            }
        }

可以看出,除了WeakHashMap<TransmittableThreadLocal, Object>类型的ttl2Value外还有一个WeakHashMap<ThreadLocal, Object>类型的threadLocal2Value。ttl2Value的作用很明显,是当前线程的所有TransmittableThreadLocal变量的集合,但是threadLocal2Value的作用就不那么清晰,通常都是空的,通过查看TTL的javadoc,发现如下一段注释:

ThreadLocal Integration
If you can not rewrite the existed code which use ThreadLocal to TransmittableThreadLocal, register the ThreadLocal instances via the methods registerThreadLocal(ThreadLocal, TtlCopier)/registerThreadLocalWithShadowCopier(ThreadLocal) to enhance the Transmittable ability for the existed ThreadLocal instances.
Below is the example code:


 // the value of this ThreadLocal instance will be transmitted after registered
 Transmitter.registerThreadLocal(aThreadLocal, copyLambda);

 // Then the value of this ThreadLocal instance will not be transmitted after unregistered
 Transmitter.unregisterThreadLocal(aThreadLocal);

从上面一段翻译而来,对于不能讲ThreadLocal转换成TransmittableThreadLocal的情况,提供了ThreadLocal集成机制,可以将ThreadLocal通过registerThreadLocalWithShadowCopier方法注册到Transmitter上的threadLocalHolder变量,从而确保该ThreadLocal会被传递到线程池中真正执行任务的线程。

private static volatile WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>> threadLocalHolder = new WeakHashMap<ThreadLocal<Object>, TtlCopier<Object>>();

TTL用法

1.修饰Runnable/Callable,

public static void main(String[] args) {
    TransmittableThreadLocal<String> transmittableThreadLocal1 = new TransmittableThreadLocal<>();
    TransmittableThreadLocal<String> transmittableThreadLocal2 = new TransmittableThreadLocal<>();
    transmittableThreadLocal1.set("parent1");
    transmittableThreadLocal2.set("parent2");
    System.out.println("in parent, current Thread is: " + Thread.currentThread().getName());
    System.out.println("in parent, transmittablThreadLocal1 is: " + transmittableThreadLocal1.get());
    System.out.println("in parent, transmittablThreadLocal2 is: " + transmittableThreadLocal2.get());

    Runnable runnable = () -> {
        System.out.println("in son,current Thread is: " + Thread.currentThread().getName());
        System.out.println("in son,transmittablThreadLocal1 is: " + transmittableThreadLocal1.get());
        System.out.println("in son,transmittablThreadLocal2 is: " + transmittableThreadLocal2.get());
    };
    TtlRunnable ttlRunnable = TtlRunnable.get(runnable);

    Executors.newCachedThreadPool().submit(ttlRunnable);
}

2.使用TtlExecutors:

public static void main(String[] args) {
    TransmittableThreadLocal<String> transmittableThreadLocal1 = new TransmittableThreadLocal<>();
    TransmittableThreadLocal<String> transmittableThreadLocal2 = new TransmittableThreadLocal<>();
    transmittableThreadLocal1.set("parent1");
    transmittableThreadLocal2.set("parent2");
    System.out.println("in parent, current Thread is: " + Thread.currentThread().getName());
    System.out.println("in parent, transmittablThreadLocal1 is: " + transmittableThreadLocal1.get());
    System.out.println("in parent, transmittablThreadLocal2 is: " + transmittableThreadLocal2.get());

    Runnable runnable = () -> {
        System.out.println("in son,current Thread is: " + Thread.currentThread().getName());
        System.out.println("in son,transmittablThreadLocal1 is: " + transmittableThreadLocal1.get());
        System.out.println("in son,transmittablThreadLocal2 is: " + transmittableThreadLocal2.get());
    };

    ExecutorService executorService = Executors.newFixedThreadPool(1);
    ExecutorService ttlExecutorService = TtlExecutors.getTtlExecutorService(executorService);
    ttlExecutorService.submit(runnable);
}

3.使用Java Agent来修饰JDK线程池实现类

// ## 1. 框架上层逻辑,后续流程框架调用业务 ##
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<String>();
context.set("value-set-in-parent");

// ## 2. 应用逻辑,后续流程业务调用框架下层逻辑 ##
ExecutorService executorService = Executors.newFixedThreadPool(3);

Runnable task = new Task("1");
Callable call = new Call("2");
executorService.submit(task);
executorService.submit(call);

// ## 3. 框架下层逻辑 ##
// Task或是Call中可以读取,值是"value-set-in-parent"
String value = context.get();

Java的启动参数配置示例:
java -javaagent:path/to/transmittable-thread-local-2.x.x.jar
-cp classes
com.alibaba.ttl.threadpool.agent.demo.AgentDemo

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`ThreadLocal` 是一个 Java 类,用于在多线程环境下为每个线程提供独立的变量副本。通常情况下,在多线程环境下共享变量可能会导致线程安全问题,而 `ThreadLocal` 可以为每个线程提供一个独立的变量副本,从而避免了这个问题。 在使用 `ThreadLocal` 时,每个线程可以通过 `get()` 方法获取到自己的变量副本,而且这个副本只能被当前线程访问和修改。每个线程都有自己独立的变量副本,不会相互干扰。 例如,下面的代码演示了如何使用 `ThreadLocal` 存储和访问一个字符串变量: ``` public class ThreadLocalDemo { private static final ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) { threadLocal.set("Hello, ThreadLocal!"); String value = threadLocal.get(); System.out.println(value); // 输出结果:Hello, ThreadLocal! } } ``` 在上面的代码中,我们定义了一个名为 `threadLocal` 的静态变量,它的类型为 `ThreadLocal<String>`,表示它可以为每个线程提供一个独立的字符串变量。然后在 `main` 方法中,我们通过 `threadLocal.set()` 方法为当前线程设置了一个字符串变量,然后通过 `threadLocal.get()` 方法获取到了这个字符串变量,并输出到控制台上。 需要注意的是,每个线程都需要通过 `get()` 方法获取自己的变量副本,并且在使用完毕后需要及时调用 `remove()` 方法将变量副本从内存中清除,以免造成内存泄漏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值