飞道的博客

ThreadLocal 系列之用简单的方式解释 ThreadLocal

258人阅读  评论(0)

本文目标:去掉高大上的专有名词,用简单的方式解释 ThreadLocal

ThreadLocal 是一门很古老的技术,在 JDK 1.2 就出现了,JDK 1.5 之后支持泛型,它协助我们操作线程级别的上下文。关于 ThreadLocal 网上已经有很多资料对其进行分析,经常可以看到这么一句话:“ThreadLocal 提供了线程的局部变量”,不知道为啥都这么说,可能是因为这个类官方注释的第一句话就是:

This class provides thread-local variables.

再看下 provide 这个单词的英文翻译:

  • v. 供给;提供;准备;规定;抚养

可以看到,还有“准备”和“规定”的意思。

不好说“ThreadLocal 提供了线程的局部变量”这句话说的对不对,但是个人感觉这个解释某种程度下可能会造成误导,就是好像这个“变量”是 ThreadLocal 提供给每个线程的一样,而且因为这个变量有所谓的不会有“线程安全问题”、“线程独有”等“特性”,就好像这个“变量”很特殊一样,但是实际上这个变量其实没有什么特殊的,就是 Thread 的一个普普通通的成员变量,而 ThreadLocal 就是帮我们操作这个变量的工具,无非就是 Thread 这个类比较特殊,可以随时随地获取实例而已。

分析 ThreadLocal 最主要就是分析所谓的“线程本地变量”是如何进行存储的。

自己实现简易版 ThreadLocal

首先可以猜想一下 ThreadLocal 是如何实现的,我当时看了“ThreadLocal 提供了线程的局部变量”这句话第一个想法就是 ThreadLocal 内部维护了一个 Mapkey 是当前线程,代码如下:

public class MyThreadLocal<T> extends ThreadLocal<T>{

    private final Map<Thread,T> HOLDER = new ConcurrentHashMap<>();

    @Override
    protected T initialValue() {
        return null;
    }

    @Override
    public T get() {
        Thread t = Thread.currentThread();
        T value = HOLDER.get(t);
        if (value == null){
            return initialValue();
        }
        return value;
    }

    @Override
    public void set(T value) {
        HOLDER.put(Thread.currentThread(),value);
    }

    @Override
    public void remove() {
        HOLDER.remove(Thread.currentThread());
    }
}

可以简单测试一下:

public class MyThreadLocalTest {

    private static final MyThreadLocal<Integer> HOLDER = new MyThreadLocal<Integer>() {
        @Override
        public Integer initialValue() {
            return 0;
        }
    };

    private static final ExecutorService POOL = Executors.newFixedThreadPool(2);

    public static void main(String[] args) {
        Runnable task = () -> IntStream.range(0, 10).forEach(i -> System.out.println(Thread.currentThread().getName() + ":" + nextInt()));
        POOL.submit(task);
        POOL.submit(task);

        POOL.shutdown();
    }

    private static Integer nextInt() {
        Integer value = HOLDER.get();
        value++;
        HOLDER.set(value);
        return value;
    }

}

会发现每个线程的变量都是隔离的,不会出现线程安全问题。这个是猜想的实现,那么看下 JDK 是如何实现的。

ThreadLocal#set

首先看看这个“变量”设置到哪去了。方法其实很简单:

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 方法实际上就是获取的 Thread 类中的一个成员变量:

ThreadLocal.ThreadLocalMap threadLocals = null;

最终值就会被设置到这个 threadLocals 成员变量中。而值就是设置到 threadLocals 中。可以看到其实为什么这个“变量”会线程安全、线程独有,并不是 ThreadLocal 给它创造的,而 ThreadLocal只是为 Thread 的成员变量赋了值,一个类实例的成员变量当然归这个类独有了,所以其实没有说的那么高大上。

欢迎关注公众号
​​​​​​


转载:https://blog.csdn.net/Dongguabai/article/details/105168136
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场