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

本项目属于机器学习的简单部分,基于为了快速理解机器学习而搭建的人工智能速成项目,大家可以根据其中的项目时间进行相关的学习.zip项目工程资源经过严格测试可直接运行成功且功能正常的情况才上传,可轻松复刻,拿到资料包后可轻松复现出一样的项目,本人系统开发经验充足(全领域),有任何使用问题欢迎随时与我联系,我会及时为您解惑,提供帮助。 【资源内容】:包含完整源码+工程文件+说明(如有)等。答辩评审平均分达到96分,放心下载使用!可轻松复现,设计报告也可借鉴此项目,该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的。 【提供帮助】:有任何使用问题欢迎随时与我联系,我会及时解答解惑,提供帮助 【附带帮助】:若还需要相关开发工具、学习资料等,我会提供帮助,提供资料,鼓励学习进步 【项目价值】:可用在相关项目设计中,皆可应用在项目、毕业设计、课程设计、期末/期中/大作业、工程实训、大创等学科竞赛比赛、初期项目立项、学习/练手等方面,可借鉴此优质项目实现复刻,设计报告也可借鉴此项目,也可基于此项目来扩展开发出更多功能 下载后请首先打开README文件(如有),项目工程可直接复现复刻,如果基础还行,也可在此程序基础上进行修改,以实现其它功能。供开源学习/技术交流/学习参考,勿用于商业用途。质量优质,放心下载使用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值