• java并发编程学习: ThreadLocal使用及原理


    多线程应用中,如果希望一个变量隔离在某个线程内,即:该变量只能由某个线程本身可见,其它线程无法访问,那么ThreadLocal可以很方便的帮你做到这一点。 

    先来看一下示例:

    package yjmyzz.test;
    
    public class ThreadLocalTest1 {
    
        public static class MyRunnable implements Runnable {
    
            private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
    
            @Override
            public void run() {
                threadLocal.set((int) (Math.random() * 100D));
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            }
        }
    
    
        public static void main(String[] args) {
            Thread t1 = new Thread(new MyRunnable(), "A");
            Thread t2 = new Thread(new MyRunnable(), "B");
            t1.start();
            t2.start();
        }
    }
    

    运行结果:

    B:48
    A:32

    即:线程A与线程B中ThreadLocal保存的整型变量是各自独立的,互不相干,只要在每个线程内部使用set方法赋值,然后在线程内部使用get就能取到对应的值。

    把这个示例稍微变化一下:

    package yjmyzz.test;
    
    
    public class ThreadLocalTest2 {
    
        public static class MyRunnable implements Runnable {
    
            private ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
    
            public MyRunnable(){
                threadLocal.set((int) (Math.random() * 100D));
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            }
    
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            }
        }
    
    
        public static void main(String[] args) {
            Thread t1 = new Thread(new MyRunnable(), "A");
            Thread t2 = new Thread(new MyRunnable(), "B");
            t1.start();
            t2.start();
        }
    }
    

    把ThreadLocal赋值的地方放在了MyRunnable的构造函数中,然后在run方法中读取该值,看下结果:

    main:1
    main:47
    A:null
    B:null

    思考一下:为什么会这样? MyRunnable的构造函数是由main主线程调用的,所以TheadLocal的set方法,实际上是在main主线程的环境中完成的,因此也只能在main主线程中get到,而run方法运行的上下文是子线程本身,由于run方法中并没有使用set方法赋值,因此get到的是默认空值null.

    ThreadLocal还有一个派生的子类:InheritableThreadLocal ,可以允许线程及该线程创建的子线程均可以访问同一个变量(有些OOP中的proteced的意味),这么解释可能理解起来比较费劲,还是直接看代码吧:

    package yjmyzz.test;
    
    
    public class ThreadLocalTest3 {
    
        private static InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<Integer>();
    
        public static class MyRunnable implements Runnable {
    
            private String _name = "";
    
            public MyRunnable(String name) {
                _name = name;
                System.out.println(name + " => " + Thread.currentThread().getName() + ":" + threadLocal.get());
            }
    
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            }
        }
    
    
        public static void main(String[] args) {
            threadLocal.set(1);
    
            System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            Thread t1 = new Thread(new MyRunnable("R-A"), "A");
            Thread t2 = new Thread(new MyRunnable("R-B"), "B");
    
            t1.start();
            t2.start();
        }
    }
    

    main:1
    R-A => main:1
    R-B => main:1
    A:1
    B:1

    观察下结果,在主线程main中设置了一个InheritableThreadLocal实例,并在main主线程中设置了值1,然后main主线程及二个子线程t1,t2均正常get到了该值。 

    实现原理:

    可以观察下ThreadLocal及Thread的源码,大致了解其实现原理:

    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)
                    return (T)e.value;
            }
            return setInitialValue();
        }
    

    从代码上看,主要思路如下:

    1.取当前线程

    2.取得ThreadLocalMap类(先不管这个的实现,从命名上看,理解成一个Map<K,V>容器即可)

    3.如果Map容器不为空,则根据ThreadLocal自身的HashCode(见后面的继续分析)取得对应的Entry(即Map里的k-v元素对)

    4.如果entry不为空,则返回值

    5.如果Map容器为空,则设置初始值

    继续顺藤摸瓜:

    ThreadLocal的getMap及ThreadLocalMap的getEntry方法

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

    可以发现getMap其实取的是Thread实例t上的一个属性,继续看Thread的代码:

        /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;
    
        /*
         * InheritableThreadLocal values pertaining to this thread. This map is
         * maintained by the InheritableThreadLocal class.
         */
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    

    说明每个Thread内部都维护着二个ThreadLocalMap,一个应对threadLocals(即:一个Thread内部可以有多个ThreadLocal实例),另一个对应着 inheritableThreadLocals,再看ThreadLocal.ThreadLocalMap的getEntry方法

            private Entry getEntry(ThreadLocal key) {
                int i = key.threadLocalHashCode & (table.length - 1);
                Entry e = table[i];
                if (e != null && e.get() == key)
                    return e;
                else
                    return getEntryAfterMiss(key, i, e);
            }
    

    从这里看,ThreadLocalMap的key是基于ThreadLocal的Hashcode与内部table的长度-1做位运算的整数值,只要有个印象,threadLocalMap的key跟ThreadLocal实例的hashcode有关即可。

    最后看看ThreadLocal的setInitialValue方法:

        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,找到内部维护的ThreadLocalMap容器,如果容器为空,则创建Map实例,否则直接把值放进去(Key跟ThreadLocal实例本身的hashCode相关)

    根据以上分析,对于ThreadLocal的内部实现,其主要思路总结如下:

    1. 每个Thread实例内部,有二个ThreadLocalMap的K-V容器实例(分别对应threadLocals及inheritableThreadLocals), 容器的元素数量,即为Thread实例里的ThreadLocal实例个数

    2. ThreadLocalMap里的每个Entry的Key与ThreadLocal实例的HashCode相关(这样,多个ThreadLocal实例就不会搞混)

    3. 每个ThreadLocal实例使用set赋值时,实际上是在ThreadLocalMap容器里,添加(或更新)一条Entry信息

    4. 每个ThreadLocal实例使用get取值时,从ThreadLocalMap里根据key取出value 

    参考文章:

    http://qifuguang.me/2015/09/02/[Java并发包学习七]解密ThreadLocal/

    http://ifeve.com/java-threadlocal%e7%9a%84%e4%bd%bf%e7%94%a8/

  • 相关阅读:
    一个Package Load Failed问题的解决方法

    .NET相关的最好东西--全球最新评价
    一年了...
    MSBuild 的简单入门
    开发基于.NET Compact Framework的程序时, 常遇到的问题(2) 为什么在Visual Studio 2003调试程序时不能通过ActiveSync连接PDA
    如何:显示用逗号分隔的项集合(摘自MSDN)
    每日一句(2008.10.8)
    每日一句(2008.10.16)
    男人这辈子
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/threadlocal-demo.html
Copyright © 2020-2023  润新知