Android开发Android开发Android开发经验谈

ThreadLocal源码解读

2021-04-05  本文已影响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对象只是作为了键来存取数据,根本就没有用到多线程的操作。

上一篇 下一篇

猜你喜欢

热点阅读