ThreadLocal源码解读

作者: 奔跑吧李博 | 来源:发表于2021-04-05 21:27 被阅读0次

每个线程都有一个ThreadLocal线程本地变量,各个线程本地变量互不干扰。TreadLocalMap类型的变量(该类是一个轻量级的Map),可以调用set(),get()方法存取值,可以贯穿整个线程生命周期。键为当前线程的id,值为Object类型。
作用:提供一个线程内公共变量,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度,让线程的本地变量进行隔离。

使用ThreadLocal示例:

public class UseThreadLocal {
    //创建threadLocal时并初始化
    private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {
        @Nullable
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

    public void start() {
        Thread[] threads = new Thread[3];
        for (int i=0;i<threads.length;i++) {
            threads[i] = new Thread(new TestThread(i));
        }
        for (int i=0;i<threads.length;i++) {
            threads[i].start();
        }
    }

    public static class TestThread implements Runnable {
        int id;

        public TestThread(int id) {
            this.id = id;
        }

        @Override
        public void run() {
            Integer s = threadLocal.get();
            s = s + id;
            threadLocal.set(s);
            System.out.println(Thread.currentThread().getName() + "  threadLocal -> value: " + threadLocal.get());
        }
    }

}

调用UseThreadLocal().start()启动线程,这里打印的结果是:


3个threadLocal存储初始值都是1,加上各自的id,分别为0,1,2,然后就打印出了1,2,3的各个值。3个ThreadLocal保存的值各不影响,各有3份副本。

不适用Threadlocal来存储的示例:

public class UseThreadLocal {
    
    static Integer threadLocal = new Integer(1);

    public void start() {
        Thread[] threads = new Thread[3];
        for (int i=0;i<threads.length;i++) {
            threads[i] = new Thread(new TestThread(i));
        }
        for (int i=0;i<threads.length;i++) {
            threads[i].start();
        }
    }

    public static class TestThread implements Runnable {
        int id;

        public TestThread(int id) {
            this.id = id;
        }

        @Override
        public void run() {
            threadLocal = threadLocal + id;
            System.out.println(Thread.currentThread().getName() + "  threadLocal -> value: " + threadLocal);
        }
    }

}

打印的结果:
本来该打印1,2,3的,结果打印了1,2,4,数据就错乱了。


源码:

进入ThreadLocal的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();
    }

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

获取当前线程然后调用了getMap(Thread t) 方法,调用了当前线程的threadLocals。进入之后,得到了ThreadLocalMap类型的成员变量,每个线程都有一个自己的ThreadLocalMap。

ThreadLocalMap类部分代码:

    static class ThreadLocalMap {
        private static final int INITIAL_CAPACITY = 16;
        private ThreadLocal.ThreadLocalMap.Entry[] table;
        private int size;
        private int threshold;

        private void setThreshold(int var1) {
            this.threshold = var1 * 2 / 3;
        }

        private static int nextIndex(int var0, int var1) {
            return var0 + 1 < var1 ? var0 + 1 : 0;
        }

        private static int prevIndex(int var0, int var1) {
            return var0 - 1 >= 0 ? var0 - 1 : var1 - 1;
        }

        ThreadLocalMap(ThreadLocal<?> var1, Object var2) {
            this.size = 0;
            this.table = new ThreadLocal.ThreadLocalMap.Entry[16];
            int var3 = var1.threadLocalHashCode & 15;
            this.table[var3] = new ThreadLocal.ThreadLocalMap.Entry(var1, var2);
            this.size = 1;
            this.setThreshold(16);
        }
        static class Entry extends WeakReference<ThreadLocal<?>> {
            Object value;

            Entry(ThreadLocal<?> var1, Object var2) {
                super(var1);
                this.value = var2;
            }
        }
}

它有一个静态内部类Entry,保存一个value,数据存储在Entry类型的数组中。

那么根据源码画出示意图:


创建一个静态的ThreadLocal变量,然后创建多个线程,通过threadLocal变量存和取数据。当调用threadLocal.set时,判断当前线程的threadLocalMap是否为空,为空去new一个ThreadLocalMap对象,存入到当前线程的threadLocals。不为空,则获取threadLocalMap调用set方法,当前ThreadLocal对象为键,存入值。当调用threadLocal.get方法时,同样先判断当前线程的threadLocalMap是否为空,不为空调用threadLocalMap的getEntry方法,使用当前ThreadLocal对象为键,取出entry对象里面的value。这样完成整个操作。

从图中可以看到,每个线程的存取,都是对自己内部唯一的threadLocalMap进行操作,ThreadLocal对象只是作为了键来存取数据,根本就没有用到多线程的操作。

相关文章

  • ThreadLocal源码解读

    一.threadlocal概述 顾名思义线程本地存储,如果定义了一个threadlocal对象,每个线程往这个th...

  • ThreadLocal源码解读

      在学习Handler消息机制中Looper源码时看到ThreadLocal这个类,发现它很强大并且很方便的实现...

  • ThreadLocal 源码解读

    ThreadLocal是什么 ThreadLocal提供了线程的局部变量,每个线程访问独立的变量副本,实现了线程的...

  • ThreadLocal源码解读

    ThreadLocal的场景是用来隔离各个线程的局部变量,各个线程之间的数值互不干扰。先查看个例子: 执行的结果如...

  • ThreadLocal源码解读

    每个线程都有一个ThreadLocal线程本地变量,各个线程本地变量互不干扰。TreadLocalMap类型的变量...

  • ThreadLocal 源码解读

    ThreadLocal 翻译就是线程局部变量,就是这个变量只存在于当前的线程,只存在于当前的线程,那么完美解决了并...

  • ThreadLocal源码解析

    ThreadLocal 1. ThreadLocal 是什么? 源码中解释到 ThreadLocal 就用于存储数...

  • Android基础进阶 - 消息机制 之ThreadLocal

    目录 ThreadLocal是什么 如何使用ThreadLocal ThreadLocal源码分析 消息机制中Lo...

  • ThreadLocal 源码在Android sdk 和JDK中

    最近看ThreadLocal源码时候,发现书上的 ThreadLocal.set() 方法的源码如下 对如valu...

  • 14-ThreadLocal类详细剖析

    ThreadLocal类详细剖析 对ThreadLocal的理解 JDK中的源码是这样描述ThreadLocal的...

网友评论

    本文标题:ThreadLocal源码解读

    本文链接:https://www.haomeiwen.com/subject/xcndkltx.html