ThreadLocal 详解

ThreadLocal 顾名思义“线程本地变量”,对应到 Java 代码就是线程私有变量,可以把它理解为对于同一个变量,在不同的线程包含不同的副本,并且各个副本之间相互独立

下面我们通过一个示例简单认识 ThreadLocal:

public class ThreadLocalTest {

    ThreadLocal<Long> lLocal = new ThreadLocal<>();
    ThreadLocal<String> sLocal = new ThreadLocal<>();

    public void set() {
        lLocal.set(Thread.currentThread().getId());
        sLocal.set(Thread.currentThread().getName());
    }

    public void get() {
        System.out.println(lLocal.get());
        System.out.println(sLocal.get());
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadLocalTest test = new ThreadLocalTest();
        test.set();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                test.set();
                test.get();
            }
        });
        t1.start();
        t1.join();
        test.get();
    }

}

上述代码包含两个线程,main 线程和 t1 线程,两个线程都调用了相同对象的 set()、get() 方法,这两个方法分别向线程本地变量中放入和取出数据,其中放入的是自身线程的 ID 和 线程名称,执行结果如下所示:

9
Thread-0
1
main

从结果可以看出:虽然两个线程调用同一个对象的同一个方法,但取到的结果因线程而异,后执行线程的 set() 操作并没有覆盖前执行线程已经放入的值,也就是说:值是线程私有的。


通过示例也可以看出,ThreadLocal 的使用主要基于 get()、set() 方法,下面我们直接看源码:

public void set(T value) {
	// 获取调用 set() 方法的线程
    Thread t = Thread.currentThread();
    // 获取一个 map
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

这里首先获取当前线程对象,根据线程对象获取 ThreadLocalMap 对象,之后以 this 为 key,value 为值,存入 map。这里 this 指代当前 ThreadLocal 对象

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

从这里也就可以看出,ThreadLocalMap 对象实际是 Thread 对象的属性变量,其中它的定义如下:

ThreadLocal.ThreadLocalMap threadLocals = null;

而 ThreadLocalMap 又是 ThreadLocal 类的内部类,也就是说,set 操作实际是基于 ThreadLocalMap 来实现的。如果 Map 还没有初始化,就调用 createMap() 方法,它的源码如下:

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

还是 ThreadLocalMap ,绕来绕去,最终都会调用到这个内部类,下面我们再看 get() 方法:

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

从代码可以看出,这里也是基于 this 从 ThreadLocalMap 中获取数据,查不到调用 setInitialValue() 方法:

private T setInitialValue() {
    T value = initialValue();
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

在 setInitialValue() 方法中,初始化 map,初始值默认为 initialValue() 方法:

protected T initialValue() {
    return null;
}

默认值是 null,一般重写该方法手动初始化默认值。到这里就可以看出 ThreadLoacl get()、set() 都基于 ThreadLocalMap 实现。我们简单总结一下:

  1. ThreadLocal 基于 ThreadLocalMap 实现,ThreadLocalMap 对象是线程类属性变量,实现在 ThreadLocal 中
  2. ThreadLocalMap 中 key 是 ThreadLocal 对象,value 才是具体副本值

也就是说,所有的线程都包含 ThreadLocalMap,该 Map 的 key 是 ThreadLocal 对象本身,value 是 ThreadLocal 定义时枚举类型 T 在该线程上的副本值。也就是说,我们可以定义多个 ThreadLocal,每个 ThreadLocal 对应一类副本值。

public class ThreadLocal<T> 

这块可能和传统预测不同,传统我们可能认为 ThreadLocal 本身就是一块公共内存,key 是线程本身,value 是对应副本值。这样每个线程都能访问自己的副本值,但它包含以下问题:

  1. 所有线程不但要从主内存同步自己的数据,还要存储别的线程的数据,内存消耗大
  2. 线程之间不私有,理论上仍可以访问到其他线程的数据

所以 TheadLocal 设计反其道而行,Map 本身保存在 Thread 对象中,保证线程私有,再通过 ThreadLocal 对象本身作为 key 去区分不同类型副本,这样每个线程只包含自己副本的值,既节省了空间,也保证了线程私有。关于 ThreadLocalMap 源码,我们下篇给出


根据前面的介绍,ThreadLocal 主要用在线程常用且互相私有的模块,常见的应用场景有:数据库连接、Session 管理。

举个例子:

private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
	public Connection initialValue() {
	    return DriverManager.getConnection(DB_URL);
	}
};

public static Connection getConnection() {
	return connectionHolder.get();
}

public void closeConnection() {
    Connection c = connectionHolder.get();
    c.close();
    connectionHolder.remove();
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值