文档章节

ThreadLocal源码笔记

JoeyXie
 JoeyXie
发布于 2016/02/02 01:56
字数 1125
阅读 33
收藏 1

        最近用到了ThreadLocal,所以看了一下JDK的源码。ThreadLocal是以空间换时间的典型,因为它避免了使用synchronized关键字来对变量加锁,从而节约了很多时间。ThreadLocal是为每个线程创建一个变量的副本,所以每个线程都可以访问这个副本的内容并修改,彼此之间的副本不会相互影响,因此看似会修改同一个变量,但实际上线程间修改的都是线程本地变量。例如下面这个数据库连接类:

public class DatabaseUtil {
    private static final ThreadLocal<Connection> CON_HOLDER = new ThreadLocal<Connection>();

    public static Connection getConn() {
        Connection con = CON_HOLDER.get();
        if(null == con) {
            try {
                con = DriverManager.getConnection(url, username, pwd);
            } catch (Exception e) {
                throw new RuntimeException(e);
            } finally {
                CON_HOLDER.set(con);
            }
        }
        return con;
    }

    // 其它函数
    ......
}

       我们可以在Servlet中调用getConn()方法去获取一个连接,然后执行相应的数据库操作(暂且忽略这种做法的不规范性,先讨论多线程访问的问题)。Servlet在Java web中是单实例多线程的执行模式,这也是为什么我们一般不在Servlet类中定义成员变量的原因,就是为了避免线程不安全。那么当多个请求到来时,每个请求都是在一个线程里面来访问servlet的方法的,因此看上去每个线程获取到的连接都是同一个实例(getConn方法类似单例模式)。但实际上并不是这样,每个请求都会在它的线程中获取到不同的Connection连接,这就是ThreadLocal的作用。首先看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();
}

      首先它获取了当前正在运行的线程,getMap()方法其实就是获取Thread中的ThreadLocalMap对象,并没有什么特别的。然后从ThreadLocalMap中根据当前ThreadLocal对象作为key去获取对应的值,接着返回。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;

     刚才的getMap()方法其实返回的就是threadLocal对象,现在Thread、ThreadLocal和ThreadLocalMap对象混在一起有点乱,下面来理清楚它们的关系:

     1. 每个Thread都有一个ThreadLocalMap对象threadLocals作为它的成员变量,它的key是ThreadLocal对象,值就是ThreadLocal中保存的值,如果一个线程访问了多个ThreadLocal对象,则ThreadLocalMap中就会有多个映射关系

     2. 每一个ThreadLocal对象都有自己唯一的hash值,这个值就是用于在ThreadLocalMap中查找对应的value的,所以以ThreadLocal对象为key就可以找到对应的线程本地的值

     3. ThreadLocalMap是ThreadLocal的一个静态内部类(就把它想象成一个普通的类就好了,只不过访问它需要通过ThreadLocal来访问),保存了很多个entry,每个entry都是一个ThreadLocal与它的值的映射关系

      说了这么多,还是贴一张图比较直观:

    从上图可以看到,每个线程都拥有一个ThreadLocalMap对象,它会保存ThreadLocal和value的映射关系,ThreadLocal就相当于一个中间人,每次线程方法它的get或set方法时,它都会首先获取当前运行的线程,然后获取线程中的ThreadLocalMap对象,由于每个ThreadLocal对象都有唯一hash值,所以以ThreadLocal为key可以在当前线程中找到它对应的值。每个线程都维护自己的ThreadLocalMap对象,这个就是真正的“副本”,通过ThreadLocal这个中介就可以保证成员变量的线程安全。每个线程都有自己的映射(即ThreadLocalMap),当线程第一次设置ThreadLocal的值的时候,其实就是在自己的map中添加一条访问的ThreadLocal对象和值的映射,创建副本,然后就可以根据该ThreadLocal对象(也就是它的hashcode)找到对应的值,而不需要访问共享的变量。

    ThreadLocal没有使用synchronized关键字,而是在Thread中维护一份本地的副本,如果每个线程都希望独享自己的成员变量时,ThreadLocal是一个好选择,但如果确实需要多线程共享一个成员变量时,那么还是需要使用synchronized关键字来加锁保证线程安全的。

© 著作权归作者所有

共有 人打赏支持
上一篇: AWS建站总结
下一篇: CGlib使用笔记
JoeyXie
粉丝 0
博文 34
码字总数 34321
作品 0
深圳
后端工程师
私信 提问
一份关于 Java、Kotlin 与 Android 的学习笔记

JavaKotlinAndroidLearn 这是一份关于 Java 、Kotlin 、Android 的学习笔记,既包含对基础知识点的介绍,也包含对一些重要知识点的源码解析,笔记的大纲如下所示: Java 重拾Java(0)-基础知...

叶应是叶
2018/08/08
0
0
111 多线程JUC包下代码分析

Java多线程系列目录(共43篇) AtomicLongFieldUpdater:通过反射+CAS实现对传入对象的指定long字段实现类似AtomicLong的操作 http://www.cnblogs.com/skywang12345/p/javathreadscategory.ht...

素雷
2017/10/31
0
0
BAT等大厂Android面试书单和知识点清单

java是Android开发的基础,在BAT的初面中,会涉及到比较多的java基础知识,所以比较重要,下面我介绍的书籍内容是由浅到深。 1.Thinking in java:这本书被称为Java的三大圣经之一,虽然书比...

android自学
2018/07/25
0
0
Android--面试中遇到的问题总结(三)

《Android 开发工程师面试指南 LearningNotes 》,作者是陶程,由梁观全贡献部分。大家可以去知乎关注这两位用心的少年。这份指南包含了大部分Android开发的基础、进阶知识,不仅可以帮助准备...

sealin
2017/02/22
0
0
JVM 深入笔记(2)内存溢出场景模拟

JVM 深入笔记(2)各内存区溢出场景模拟 作者:柳大 · Poechant 电邮:zhongchao.ustc#gmail.com (#->@) 博客:blog.csdn.net/poechant 日期:Feb. 23st 2012 《JVM 深入笔记(1)内存区域是...

晨曦之光
2012/04/24
126
0

没有更多内容

加载失败,请刷新页面

加载更多

Linux 权限

权限 0 000 --- 无权限 1 001 --x 执行权限 2 010 -w- 写权限 3 011 -wx 写和执行 4 100 r-- 读权限 5 101 r-x 读和执行 6 110 rw- 读和写 7 111 rwx 读写执行 755 : rwxr-xr-x 660 : rw-r...

忙碌的小蜜蜂
17分钟前
0
0
21分钟教会你分析MaxCompute账单

21分钟教会你分析MaxCompute账单 背景 阿里云大计算服务MaxCompute是一款商业化的大数据分析平台,其计算资源有预付费和后付费两种计费方式。并且产品每天按照project为维度进行计量计费(账...

阿里云云栖社区
21分钟前
0
0
Docker使用 linuxserver/letsencrypt 生成SSL证书最全解析及实践

本文使用 HTTP 和 DNS 两种校验方式对 Docker 下 linuxserver/letsencrypt 项目进行了实践。生成SpringBoot可用证书,使用 Nginx 的 htpasswd 来对网站进行密码保护,并测试使用 fail2ban 防...

java菜分享
21分钟前
0
0
代码吃鸡:Python-Robocode

最近看到一个很有“未来感”的新闻: 一辆特斯拉在拉斯维加斯出了车祸,撞“死”了一个……emmmm……机器人。不知道是意外还是炒作,又或者是这位机器人故意碰瓷,反正人们也无法从受害者口中...

crossin
25分钟前
0
0
什么是公网IP、内网IP和NAT转换?

搞网络通信应用开发的程序员,可能会经常听到外网IP(即互联网IP地址)和内网IP(即局域网IP地址),但他们的区别是什么? 1、引言 搞网络通信应用开发的程序员,可能会经常听到外网IP(即互联网I...

Linux就该这么学
35分钟前
0
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部