转载

一步一步带你阅读ThreadLocal源码(一)—— set方法

之前把《现代操作系统》的前四章看完了,收获还是很大的,尤其在进程管理那一章让我明白了并发在操作系统上是如何控制的。所以,由此也引发了这一篇源码解析文章的创作。原本是打算写AQS的源码解析的,但是最近项目比较忙,没有时间,只能先写比较简单的ThreadLocal。

PS:最近都在看《计算机网络 谢希仁编著》 ,感觉跟之前看的完全不是一本书,就是怎么变得那么简单了,可能这就是境界上的提升吧,如果LOL的段位也有那么简单提升就好了,哈哈哈哈(可能是我依然没有领悟它的精髓)。

简单使用

ThreadLocal 的使用是非常简单的

public class TestThreadLocal {
    private static ThreadLocal<String> local = new ThreadLocal<>();
    private static void print(String t){
        System.out.println(t + ":" + local.get());
    }

    public static void main(String[] args) {
        Thread threadOne = new Thread(() -> {
            local.set("threadOne local variable");
            print("threadOne");
        });
        Thread threadTwo = new Thread(() -> {
            local.set("threadTwo local variable");
            print("threadTwo");
        });
        threadOne.start();
        threadTwo.start();
    }
}
复制代码

这段代码的输出结果就是

threadOne: threadOne local variable

threadTwo: threadTwo local variable

代码执行流程:

线程 threadOne 通过 local.set("threadOne local variable") 在线程内部设置了一个值,然后通过 local.get() 可以获取到这个在线程内部的值。

线程 threadTwo 同理

为什么在两个线程内部调用同一个 local.get() 方法却可以获取到不同的值?

在解释这个问题之前,我们先来讨论另一个问题,通过 local.set() 方法设置的值到底存放在哪里?

set方法

我们先来看一下java的源码,都是大佬写的,要细细的品,给大佬应有的尊重!

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}
复制代码

虽说是大佬写的代码,但是从表面上来看,还是很简单的,哈哈哈哈!

我梳理一下这段代码都做了什么:

getMap(t)
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
复制代码

这段代码返回了线程对象中的 threadLocals 变量。

纳尼???线程对象里面竟然还有这个变量?

没错,它确实存在着,如果你只阅读过 Thread 类的源码,确实可能会把这个变量忽略,因为 Thread 类中的绝大部分方法都是跟该变量无关的。

我使用的是jdk8,所以在 Thread 类中的181行就是该变量的声明。

一步一步带你阅读ThreadLocal源码(一)—— set方法
大佬也对这个变量做了注释,大意是:该变量是与此线程有关的ThreadLocal值。而这个map是由 ThreaLocal 类维护的。这里的map就是指 ThreadLocalMap

ThreadLocalMap是一个定制的Map,实际的使用上跟我们的HashMap是类似的,只是多了一个内部类Entry,该类是一个弱引用类,这个会在下一篇的get方法讲,这里就把map当作是一个我们平时使用的Map就可以了。

所以我们大概可以回答上面提出的那个问题,值到底存放在哪里?从这里,我们知道值是存放在 Thread 线程对象里面的。

实际上值是存在ThreadLocal的ThreadLocalMap里面,而map又存放在线程对象里面,所以间接的存放在线程对象里面

为什么要用Map来存呢?我觉得应该是因为一个线程对象可以有很多个 ThreaLocal 对象。List也可以存多个值,但是使用List来存的话,我们取值时就不知道应该取哪一个值了。

让我们言归正传,继续看set方法

  • 获取到map之后会进行判断:

如果map不为空,那么就将ThreadLocal对象作为键,参数value作为值,通过map的 set() 方法进行设置。

if (map != null) {
    map.set(this, value);
} else {
    createMap(t, value);
}
复制代码

如果map为空,那么就对map进行初始化。

void createMap(Thread t, T firstValue) {
    // 创建一个ThreadLocalMap,并且设置第一个键值对,等同于map.set(this, value)
    // 然后赋值给当前线程对象的threadLocals变量
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}
复制代码

map将ThreadLocal对象作为键,那我们就可以大胆猜测测试代码中 local.get() 方法是怎么获取值的。

  • 首先获取当前线程对象
  • 然后获取当前线程中的threadLocals变量
  • 因为threadLocals是一个以ThreadLocal对象作为键的map结构,所以我们只要将this(调用get()的ThreadLocal对象)作为参数传递到map的get方法中就可以获取到对应的值。

ThreadLocal的get()基本步骤是这样的,但是因为传递进来的value实际上是要被弱引用的,所以具体实现会有一些不同。至于弱引用我会在后面单独作为一个专栏来讲。

原文  https://juejin.im/post/5df89774e51d4557ea02b256
正文到此结束
Loading...