ThreadLocal源码解析及使用场景

概述

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).
ThreadLocal类用来提供线程内部的副本变量。这种变量在多线程环境下访问(通过get和set方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是private static类型的,用于关联线程和线程上下文。

源码浅析

方法声明描述
public ThreadLocal()创建线程副本变量
public T get()获取当前线程副本变量
public void set(T value)设置当前线程副本变量
public void remove()移出当前线程绑定副本变量

我们一起看下这几个关键方法,我将注释直接标记在代码上方便理解

public class ThreadLocal<T> {
    /**
     * ThreadLocals rely on per-thread linear-probe hash maps attached
     * to each thread (Thread.threadLocals and
     * inheritableThreadLocals).  The ThreadLocal objects act as keys,
     * searched via threadLocalHashCode.  This is a custom hash code
     * (useful only within ThreadLocalMaps) that eliminates collisions
     * in the common case where consecutively constructed ThreadLocals
     * are used by the same threads, while remaining well-behaved in
     * less common cases.
     */
    private final int threadLocalHashCode = nextHashCode();

    /**
     * The next hash code to be given out. Updated atomically. Starts at
     * zero.
     */
    private static AtomicInteger nextHashCode =
            new AtomicInteger();

    /**
     * The difference between successively generated hash codes - turns
     * implicit sequential thread-local IDs into near-optimally spread
     * multiplicative hash values for power-of-two-sized tables.
     */
    private static final int HASH_INCREMENT = 0x61c88647;

    /**
     * Returns the next hash code.
     */
    private static int nextHashCode() {
        return nextHashCode.getAndAdd(HASH_INCREMENT);
    }

   
    protected T initialValue() {
        return null;
    }

  

    /**
     * Creates a thread local variable.
     * @see #withInitial(java.util.function.Supplier)
     */
    public ThreadLocal() {
    }

    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
    	//获得对应的线程
        Thread t = Thread.currentThread();
        //通过线程获得对应的map
        java.lang.ThreadLocal.ThreadLocalMap map = getMap(t);
        //判断map是否为空,不为空则拿到对应线程作为Key的value
        if (map != null) {
            java.lang.ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        //map为空,返回初始值
        return setInitialValue();
    }

   
    /**
     * Variant of set() to establish initialValue. Used instead
     * of set() in case user has overridden the set() method.
     *
     * @return the initial value
     */
    private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        java.lang.ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        //通过当前线程,获得map然后进行map的set方法
        java.lang.ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
        	//没有则创建新的map
            createMap(t, value);
        }
    }

    /**
     * Removes the current thread's value for this thread-local
     * variable.  If this thread-local variable is subsequently
     * {@linkplain #get read} by the current thread, its value will be
     * reinitialized by invoking its {@link #initialValue} method,
     * unless its value is {@linkplain #set set} by the current thread
     * in the interim.  This may result in multiple invocations of the
     * {@code initialValue} method in the current thread.
     *
     * @since 1.5
     */
    public void remove() {
        java.lang.ThreadLocal.ThreadLocalMap m = getMap(Thread.currentThread());
        if (m != null) {
            m.remove(this);
        }
    }  
}


简单来说,ThreadLocal就像一个大的仓库,每个线程有一个自己的保险柜,线程可以通过自己的唯一标识找到自己的保险柜,并且在自己的保险柜中存取东西,随着线程的消失,自己的保险柜也消失了。代码并不难,很容易看懂。

工具类

下面是个简单的工具类,需要自取。工具类封装了需要用到的基本方法,其他请自行拓展。

import java.util.HashMap;
import java.util.Map;

/***
 * scott
 */
public class ThreadLocalUtil {

    private static final ThreadLocal<Map<Object, Object>> THREAD_LOCAL = new ThreadLocal<Map<Object, Object>>() {
        @Override
        protected Map<Object, Object> initialValue() {
            return new HashMap<Object, Object>();
        }
    };

    /**
     * 根据key获取值
     *
     * @param key
     * @return
     */
    public static Object getValue(Object key) {
        if (THREAD_LOCAL.get() == null) {
            return null;
        }
        return THREAD_LOCAL.get().get(key);
    }

    /**
     * 存储
     *
     * @param key
     * @param value
     * @return
     */
    public static Object setValue(Object key, Object value) {
        Map<Object, Object> cacheMap = THREAD_LOCAL.get();
        if (cacheMap == null) {
            cacheMap = new HashMap<Object, Object>();
            THREAD_LOCAL.set(cacheMap);
        }
        return cacheMap.put(key, value);
    }

    /**
     * 根据key移除值
     *
     * @param key
     */
    public static void removeValue(Object key) {
        Map<Object, Object> cacheMap = THREAD_LOCAL.get();
        if (cacheMap != null) {
            cacheMap.remove(key);
        }
    }

    /**
     * 重置
     */
    public static void reset() {
        if (THREAD_LOCAL.get() != null) {
            THREAD_LOCAL.get().clear();
        }
    }


    public static boolean containsKey(Object key) {
        return THREAD_LOCAL.get().containsKey(key) ? Boolean.TRUE : Boolean.FALSE;
    }
}

使用场景

举个栗子🌰🌰🌰:
1.数据源切换。
2.多层之间参数传递,例如微服务请求的requestId(或traceId)
   其实不难看出,其使用场景都是围绕着他的原理进行的,在微服务模块中用于唯一标记一次请求的全局唯一UUID,在不同模块中传递相同的traceid是通过RPC框架封装并且序列化/反序列化实现的,而RPC框架反序列化出traceId后就会将其放入到ThreadLocal中,这样在模块的各个阶段记录log时都可以通过该ID标识出该请求。并且随着微服务框架的完成,也可以通过一个服务将traceId串联起来,分析一次请求中各个阶段的状态以及耗时。
   我最近遇到一个使用场景就是,我在调用其他方法时,想传入一个参数,但是又不想重写方法增加入参,因此使用这个工具类,在调用之前设置了当前用户id,然后直接侵入调用方法,拿到ThreadLocal进行判断并且获取变量。

    public void init(ProjectScoreEmpower projectScoreEmpower, int year, int quarter, Project project) throws Exception {
        Long userId = projectScoreEmpower.getEmpowerUserId();
        // 这里将user_id设置进当前线程副本变量
        ThreadLocalUtil.setValue("current_user_id", userId);
        String projectId = project.getUuid();
    }
    
	//这是另一层的代码中的私有方法
	private String getCurrentUserId(){
		//这里我先从当前线程副本变量中判断并获取当前用户id
        if(ThreadLocalUtil.containsKey("current_user_id")){
            String current_user_id = ThreadLocalUtil.getValue("current_user_id").toString();
            return current_user_id;
        }
        //没有,说明上层未传递,则通过其他方式获取。
        return userDataManager.getCurrentUserTypeAndId();
    }

怎么样?非常简单吧!!!
点赞收藏,富婆包养✋✋

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码大师麦克劳瑞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值