多线程应用中,如果希望一个变量隔离在某个线程内,即:该变量只能由某个线程本身可见,其它线程无法访问,那么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/

java并发编程学习: ThreadLocal使用及原理的更多相关文章

  1. Java并发编程学习前期知识下篇

    Java并发编程学习前期知识下篇 通过上一篇<Java并发编程学习前期知识上篇>我们知道了在Java并发中的可见性是什么?volatile的定义以及JMM的定义.我们先来看看几个大厂真实的 ...

  2. Java并发编程:Synchronized及其实现原理

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  3. Java并发编程:ThreadLocal

    Java并发编程:深入剖析ThreadLocal   Java并发编程:深入剖析ThreadLocal 想必很多朋友对ThreadLocal并不陌生,今天我们就来一起探讨下ThreadLocal的使用 ...

  4. Java并发编程学习路线(转)

    以前特地学过并发编程,但是没怎么学进去,不太喜欢.最近发现,作为一个资深工程师,却没有完整深入系统的学习过,而反是现在的BAT大并发是必须的,感觉甚是惭愧. 故找了一片学习文章,如下,准备集中一段时间 ...

  5. Java并发编程学习路线

    一年前由于工作需要从微软技术栈入坑Java,并陆陆续续做了一个Java后台项目,目前在搞Scala+Java混合的后台开发,一直觉得并发编程是所有后台工程师的基本功,所以也学习了小一年Java的并发工 ...

  6. Java并发编程学习笔记

    Java编程思想,并发编程学习笔记. 一.基本的线程机制 1.定义任务:Runnable接口 线程可以驱动任务,因此需要一种描述任务的方式,这可以由Runnable接口来提供.要想定义任务,只需实现R ...

  7. Java并发指南开篇:Java并发编程学习大纲

    Java并发编程一直是Java程序员必须懂但又是很难懂的技术内容. 这里不仅仅是指使用简单的多线程编程,或者使用juc的某个类.当然这些都是并发编程的基本知识,除了使用这些工具以外,Java并发编程中 ...

  8. 学习笔记:java并发编程学习之初识Concurrent

    一.初识Concurrent 第一次看见concurrent的使用是在同事写的一个抽取系统代码里,当时这部分代码没有完成,有许多的问题,另一个同事接手了这部分代码的功能开发,由于他没有多线程开发的经验 ...

  9. Java并发编程学习:volatile关键字解析

    转载:https://www.cnblogs.com/dolphin0520/p/3920373.html 写的非常棒,好东西要分享一下 Java并发编程:volatile关键字解析 volatile ...

随机推荐

  1. SqlServer灾备方案(本地)

    如果你曾经有那么一个不经意的心跳来自于数据库数据损坏:错误的新增.更新.删除 .那么下面的方案一定能抚平你的创伤! 对于一个数据库小白来说,数据库的任何闪失带来的打击可说都是致命的.最初,我们让一个叫 ...

  2. CSS、j's单行、多行文本溢出显示省略号

    在项目中,由于实际描述文字过多,导致初始页面纵向长度过长,也使得余下信息利用率降低:所以在文字过多的时候,初始化限制行数是有必要的 1. CSS单行文本溢出,显示省略号 <div style=& ...

  3. AMD and CMD are dead之KMD规范

    What's KMD? 乱世出英雄,KMD名字的由来充满了杀气. Kill AMD and CMD KMD为替代混乱的AMD和CMD世界而生,一统天下.或者让这个混乱的世界更加混乱,导致: KMD A ...

  4. hexo博客进阶-相册和独立域名

    之前我已经写了一篇文章详细的讲述了如何使用hexo搭建github博客.如果还没有看的可以去看看,hexo搭建博客 其实,根据这篇文章的过程我们就能够搭建一个专属于自己,并且非常美观的博客了.但是如果 ...

  5. 偷偷发请求的ajax

    概述 对于WEB应用程序:用户浏览器发送请求,服务器接收并处理请求,然后返回结果,往往返回就是字符串(HTML),浏览器将字符串(HTML)渲染并显示浏览器上. 1.传统的Web应用 一个简单操作需要 ...

  6. One-Time Project Recognition

    Please indicate the source if you need to repost. After implementing NetSutie for serveral companies ...

  7. Sharepoint学习笔记—习题系列--70-576习题解析 -(Q128-Q130)

    Question  128 You are designing a SharePoint 2010 solution that includes a custom site definition an ...

  8. 用UILocalNotification实现一个闹钟(Swift)

    之前项目需求要实现一个闹钟,github上找了半天发现都是很旧的代码了,所以就准备自己写一个,刚好最近在学习Swift,就用Swift写了一个demo放在这里:https://github.com/P ...

  9. 初学HTML 常见的标签(三) 插入类标签

    第三篇博客, 这次说的是插入链接类标签, 我们平常在网页中经常能看到蓝色的链接类标签, 或者是一张图片, 一个电邮, 这些都是插入链接类的标签起的作用. <a></a>链接标签 ...

  10. iOS---用Application Loader 上传的时候报错No suitable application records were found. Verify your bundle identifier 'xx' is correct

    用Application Loader 上传的时候报错,突然发现用Application Loader的账号 竟然不是公司的账号  换成公司的账号 就可以了.