Java多线程 - ThreadLocal源码解析

1. ThreadLocal内部结构

在早期的JDK版本中,ThreadLocal的内部结构是一个Map,其中每一个线程实例作为Key,线程在“线程本地变量”中绑定的值为Value,每一个ThreadLocal实例拥有一个Map实例。
在这里插入图片描述
在JDK 8版本中,每一个Thread线程内部都有一个ThreadLocalMap,其中ThreadLocal实例为Key,本地数据为Value。如果给一个Thread创建多个ThreadLocal实例,然后放置本地数据,那么当前线程的ThreadLocalMap中就会有多个“Key-Value对”
在这里插入图片描述
从代码的层面来说,新版本的ThreadLocalMap还是由ThreadLocal类维护的,由ThreadLocal负责ThreadLocalMap实例的获取和创建,并从中设置本地值、获取本地值。所以ThreadLocalMap还寄存于ThreadLocal内部,并没有被迁移到Thread内部。

每一个线程在获取本地值时,都会将ThreadLocal实例作为Key从自己拥有的ThreadLocalMap中获取值,别的线程无法访问自己的ThreadLocalMap实例,自己也无法访问别人的ThreadLocalMap实例,达到相互隔离,互不干扰。

@Data
public class Person {
    private int age = 10;
}
public class TestThreadLocal {
    private static final ThreadLocal<Person> threadLocal = new ThreadLocal<Person>();
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        // 提交5个任务,使用5个线程
        for(int i=0;i<5;i++){
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    // 获取threadLocal中当前线程绑定的值
                    if(threadLocal.get()==null){
                        // 设置threadLocal中当前线程绑定的值
                        threadLocal.set(new Person());
                    }
                    System.out.println("初始的本地值:"+threadLocal.get());
                    // 每个线程执行10次
                    for(int i=0;i<10;i++){
                        Person foo = threadLocal.get();
                        foo.setAge(foo.getAge()+1);
                        // 睡眠1s
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("累加10次之后的本地值:"+threadLocal.get());
                    // 删除threadLocal中当前线程所绑定的值,这点对于线程池中的线程特别重要
                    threadLocal.remove();
                }
            });
        }
    }
}

2. ThreadLocal源码分析

1. Thread类源码:

public class Thread implements Runnable {
    // 每一个Thread线程内部都有一个Map(ThreadLocalMap)
    // 如果给一个Thread创建多个ThreadLocal实例,然后放置本地数据,
    // 那么当前线程的ThreadLocalMap中就会有多个“Key-Value对”,
    // 其中ThreadLocal实例为Key,本地数据为Value。
    ThreadLocal.ThreadLocalMap threadLocals = null;
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

2. ThreadLocal类源码:

ThreadLocal源码提供的方法不多,主要有:set(T value)方法、get()方法、remove()方法。

1. set(T value)方法

set(T value)方法用于设置“线程本地变量”在当前线程的ThreadLocalMap中对应的值,相当于设置线程本地值,其核心源码如下:

public void set(T value) {
    // 获取当前线程对象
    Thread t = Thread.currentThread();
    // 获取当前线程的ThreadLocalMap成员
    ThreadLocalMap map = getMap(t);
    // 如果map不为null,将value绑定到threadLocal实例上(threadLocal,value)
    if (map != null)
        map.set(this, value);
    else
        // 如果当前线程没有ThradLocalMap成员实例,创建一个ThreadLocalMap实例,然后作为成员关联到t
        createMap(t, value);
}

// 获取线程t的ThreadLocalMap成员
// 每一个线程在获取本地值时,都会将ThreadLocal实例作为Key从自己拥有的ThreadLocalMap中获取值
// 别的线程无法访问自己的ThreadLocalMap实例,自己也无法访问别人的ThreadLocalMap实例,达到相互隔离,互不干扰
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// 线程t创建一个ThreadLocalMap成员
// 并为新的map成员设置第一个key-value对,key为当前的ThreadLocal实例
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

通过以上源码可以看出set(T value)方法的执行流程,大致如下:

(1)获得当前线程,然后获得当前线程的ThreadLocalMap成员,暂存于map变量。

(2)如果map不为空,就将Value设置到map中,当前的ThreadLocal作为Key。

(3)如果map为空,为该线程创建map,然后设置第一个“Key-Value对”,Key为当前的ThreadLocal实例,Value为set()方法的参数value值。

2. get()方法

get()方法用于获取“线程本地变量”在当前线程的ThreadLocalMap中对应的值,相当于获取线程本地值,其核心源码如下:

public T get() {
    // 获取当前线程对象
    Thread t = Thread.currentThread();
    // 获取线程对象的ThreadLocalMap成员
    ThreadLocalMap map = getMap(t);
    // 判断map是否为null
    if (map != null) {
        // 以当前threadLocaL为key获取值
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    // 如果当前线程对应的map不存在
    // 或者当前线程对应的map存在,但是当前ThreadLocal中没有对应的key-value对,返回初始值
    return setInitialValue();
}

// 设置ThreadLocal关联的初始值并返回
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;
}

通过以上源码可以看出T get()方法的执行流程,大致如下:

(1)先尝试获得当前线程,然后获得当前线程的ThreadLocalMap成员,暂存于map变量。

(2)如果获得的map不为空,那么以当前ThreadLocal实例为Key尝试获得map中的Entry(条目)。

(3)如果Entry不为空,就返回Entry中的Value。

(4)如果Entry为空,就通过调用initialValue初始化钩子函数获取ThreadLocal初始值,并设置在map中。如果map不存在,还会给当前线程创建新ThreadLocalMap成员,并绑定第一个“Key-Value对”。

3. remove()方法

remove()方法用于在当前线程的ThreadLocalMap中移除“线程本地变量”所对应的值,其核心源码如下:

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

3. ThreadLocalMap源码分析

ThreadLocal的操作都是基于ThreadLocalMap展开的,而ThreadLocalMap是ThreadLocal的一个静态内部类,其实现了一套简单的Map结构。

ThreadLocalMap的成员变量与HashMap的成员变量非常类似,其内部的主要成员如下:

static class ThreadLocalMap {
    // map的初始容量
    private static final int INITIAL_CAPACITY = 16;

    // map的条目数,作为哈希表使用
    private Entry[] table;

    // map的条目数
    private int size = 0;

    // 扩容因子
    private int threshold; // Default to 0
}

ThreadLocal源码中的get()、set()、remove()方法都涉及ThreadLocalMap的方法调用,主要调用了ThreadLocalMap的如下几个方法:

(1)set(ThreadLocal<?> key,Object value):向Map实例设置“Key-Value对”。

(2)getEntry(ThreadLocal):从Map实例获取Key(ThreadLocal实例)所属的Entry。

(3)remove(ThreadLocal):根据Key(ThreadLocal实例)从Map实例移除所属的Entry。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

我一直在流浪

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

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

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

打赏作者

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

抵扣说明:

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

余额充值