ThreadLocal源码浅析

 
ThreadLocal不是一个具体的线程。它是一个线程内部的数据存储类,通过它可以再指定的线程中存储数据,数据存储以后,只有在指定线程中可以获取到存储的数据,对于其它线程来说则无法获取到数据。

  ThreadLocal之所以有这么神奇的效果,是因为不同线程访问同一个ThreadLocal的get方法,ThreadLocal内部会将各自线程的引用当做table数组的一个值存在,然后从数组中根据当前ThreadLocal的reference去查找出相应的value。这就是为什么通过ThreadLocal可以再不同线程中维护一套数据的副本并且彼此互不干扰。

  在java中ThreadLocal以Map的形式存储数据(ThreadLocal对象为 key  数值为value)。在Android中做了些改动,在Thread-Local的add方法中,可以看到它会把ThradLocal对象(key)和相对应的value放在table数组连续的位置中。 也就是table被设计为下标为0,2,4...2n的位置存放key,而1,3,5...(2n +1 )的位置存放value。

void add(ThreadLocal<?> key, Object value) {
            for (int index = key .hash & mask ;; index = next(index )) {
                Object k = table[ index];
                if (k == null) {
                    table[ index] = key. reference;
                    table[ index + 1] = value;
                    return;
                }
            }
        }

类中最重要的两个方法是get(),set()。下面开始分析set()源码。

    public void set(T value ) {
        Thread currentThread = Thread.currentThread();
        Values values = values( currentThread);
        if (values == null) {
            values = initializeValues(currentThread );
        }
        values.put( this, value );
    }

首先获取当前线程对象currentThread,然后执行values(currentThread)方法。源码如下:

Values values(Thread current) {
      return current .localValues;
  }

     在values (currentThread )中返回了currentThread
.localValues。跟进Thread的源码可以发现:这个currentThread .localValues其实就是ThreadLocal.Values  localValues 。Values
是ThreadLocal中的一个静态内部类。此时获取到返回的Values对象。

     接下来进行判空操作,如果返回的values为空,那么再次实例化currentThread。跟进initializeValue-s(currentThread)可以发现

Values initializeValues(Thread current) {
        return current .localValues = new Values();
    }

new Values()的实例化过程:

       Values() {
            initializeTable( INITIAL_SIZE);//INITIAL_SIZE 默认值16
            this.size = 0;
            this.tombstones = 0;
        }

      private void initializeTable( int capacity ) {
            this.table = new Object[capacity * 2];
            this.mask = table .length - 1;
            this.clean = 0;
            this.maximumLoad = capacity * 2 / 3; // 2/3
        }

此时可以确保有了Values的一个实例,接下来就可以执行values
.put(this, value ),跟进put方法

void put(ThreadLocal<?> key, Object value) {
            cleanUp();

            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;

            for (int index = key .hash & mask ;; index = next(index )) {
                Object k = table[ index];

                if (k == key .reference ) {
                    // Replace existing entry.
                    table[ index + 1] = value;
                    return;
                }

                if (k == null) {
                    if (firstTombstone == -1) {
                        // Fill in null slot.
                        table[ index] = key.reference ;
                        table[ index + 1] = value;
                        size++;
                        return;
                    }

                    // Go back and replace first tombstone.
                    table[ firstTombstone] = key.reference ;
                    table[ firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                }

                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index ;
                }
            }
        }

 依据上面的代码可以得出一个存储规则:ThreadLocal的值在table数组中的存储位置总是为reference字段所表示的对象的下一个位置。

table[index]
=key.reference;

table[index+
1] =value;

最终ThreadLocal的值会被存储在table数组中:table[index+
1] =value;至此,set方法解析完毕。下面看一下get方法。

public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values( currentThread);
        if (values != null) {
            Object[] table = values. table;
            int index = hash & values .mask ;
            if (this .reference == table [index ]) {
                return (T) table [index + 1];
            }
        } else {
            values = initializeValues(currentThread );
        }

        return (T) values .getAfterMiss(this);
    }

看完set方法后再看get就比较简单了,首先得到一个Values对象,然后求出table数组ThreadLocal.reference的下标。前文说过:ThradLocal对象(key)和相对应的value放在table数组连续的位置中。
也就是table被设计为下标为0,2,4...2n的位置存放key,而1,3,5...(2n +1 )的位置存放value。现在得到index后再index+1就是value在table数组中的下标。即value=table[index+1];return value即可。

到此想必读者对ThreadLocal为什么能在不同线程中能够为不同线程创建不同的线程副本(其实不太准确,应该是相同对象的不同值),原因就在于采用了key value形式的table数组。key为不同线程的reference,value就五花八门了。

        ThreadLocal浅析到此结束。谢谢欣赏~

时间: 2025-01-05 13:07:31

ThreadLocal源码浅析的相关文章

PgSQL · 最佳实践 · pg_rman源码浅析与使用

背景 对于商业数据库来说,备份的功能一般都非常的全面. 比如Oracle,它的备份工具rman是非常强大的,很多年前就已经支持全量.增量.归档的备份模式,支持压缩等. 还支持元数据存储到数据库中,管理也非常的方便,例如保留多少归档,备份集的管理也很方便,例如要恢复到什么时间点,将此前的备份清除等等. 对于开源数据库来说,支持向商业版本这么丰富功能的比较少,PostgreSQL算是非常完善的一个. PostgreSQL作为最高级的开源数据库,备份方面已经向商业数据库看齐. 目前PostgreSQL

我对java String的理解 及 源码浅析

一.char说起到String 这也是自己第二次回过头来啃java基础书,小生自认为愚昧无知.如果大神有好的教育,可以评论私信.以下都是我的看法: 为什么说char 呢,我这里先卖个关子.在java中,char是用unicode编码的,占16位(2字节).从ansi编码(1字节)到unicode编码(2字 节).Java中使用Unicode的原因是,Java的Applet(网页)运行,Unicode里面包含最多最广比如:中 文,English,Spanish,German, French等.因此

Android源码浅析(一)——VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置

Android源码浅析(一)--VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置 最近地方工作,就是接触源码的东西了,所以好东西还是要分享,系列开了这么多,完结 的也没几个,主要还是自己覆盖的太广了,却又不精通,嘿嘿,工作需要,所以写下了本篇博客 一.VMware 12 我选择的虚拟机试VMware,挺好用的感觉,下载VMware就不说了,善用搜索键嘛,这里我提供一个我现在在用的 下载地址:链接:http://pan.baidu.com/s/1k

苹果推出最受欢迎的iOS 到 民用与商用数据库备份的差异与源码浅析

背景 苹果推出了有史以来最受欢迎的一版iOS,为什么这么受欢迎? 最主要的还是使用了最新的APFS文件系统,这个文件系统几乎集成了ZFS,Btrfs的所有优良特性,比如最为好用的快照(块级增量).压缩.使得苹果的操作系统一下子瘦了,而且备份占用空间也非常小. 对于数据库来说,备份也不是小事,如何实现高效的备份.节省空间的备份以及具备可以定义SLA的恢复(不会随着数据库的大小.REDO的多少而变化). 对于商业数据库来说,备份的功能一般都非常的全面. 比如Oracle,它的备份工具rman是非常强

PostgreSQL 9.6 快照过旧 - 源码浅析

PostgreSQL 9.6 快照过旧 - 源码浅析 作者 digoal 日期 2016-10-05 标签 PostgreSQL , 9.6 , 快照过旧 , snapshot too old 背景 在PostgreSQL 9.6以前,垃圾回收存在的问题. 当vacuum回收垃圾时,遇到垃圾记录的xmax大于数据库中现存的最早未提交事务xmin时,不会对其进行回收. 因此当数据库中存在很久为结束的事务时,可能会导致数据库膨胀. PostgreSQL 9.6加入了快照过旧的功能,目的是防止过长的事

Android源码浅析(二)——Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境

Android源码浅析(二)--Ubuntu Root,Git,VMware Tools,安装输入法,主题美化,Dock,安装JDK和配置环境 接着上篇,上片主要是介绍了一些安装工具的小知识点Android源码浅析(一)--VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置,其实Ubuntu Kylin 16.04 LTS也只是为了体验,我们为了追求稳定,还是使用了Ubuntu14.04 这里提供一个国内镜像的下载链接,可以用迅雷,下载下来之后后缀

【深入浅出jQuery】源码浅析2--奇技淫巧

最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐进增强)优雅的处理能力以及 Ajax 等方面周到而强大的定制功能无不令人惊叹. 另外,阅读源码让我接触到了大量底层的知识.对原生JS .框架设计.代码优化有了全新的认识,接下来将会写一系列关于 jQuery 解析的文章. 我在 github 上关于 jQuery 源码的全文注解,感兴趣的可以围观一下

Android源码浅析(四)——我在Android开发中常用到的adb命令,Linux命令,源码编译命令

Android源码浅析(四)--我在Android开发中常用到的adb命令,Linux命令,源码编译命令 我自己平时开发的时候积累的一些命令,希望对你有所帮助 adb是什么?: adb的全称为Android Debug Bridge,就是起到调试桥的作用.通过adb我们可以在IDE中调试Android程序,说白了就是debug工具.adb的工作方式比较特殊,采用监听Socket TCP 5554等端口的方式让IDE和Qemu通讯,默认情况下adb会daemon相关的网络端口,所以当我们运行And

Android源码浅析(三)——Android AOSP 5.1.1源码的同步sync和编译make,搭建Samba服务器进行更便捷的烧录刷机

Android源码浅析(三)--Android AOSP 5.1.1源码的同步sync和编译make,搭建Samba服务器进行更便捷的烧录刷机 最近比较忙,而且又要维护自己的博客,视频和公众号,也就没仔细的梳理源码的入门逻辑,今天也就来讲一个源码的玩法,各位看官,一起学习学习! 看本篇博客之前,先看下我的前面两篇 Android源码浅析(一)--VMware Workstation Pro和Ubuntu Kylin 16.04 LTS安装配置 Android源码浅析(二)--Ubuntu Roo