首页 > 编程语言 >ThreadLocal源码

ThreadLocal源码

时间:2023-07-09 22:33:32浏览次数:40  
标签:Thread ThreadLocalMap threadLocal value ThreadLocal 源码 线程

使用场景

ThreadLocal用来提供线程局部变量。每个线程都会有一份独立的副本,副本之间不存在竞争关系,是线程专属的内存空间。
例如:

public class ThreadLocalTest {

    private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            threadLocal.set(0);
            for (int i = 0; i < 100; i++) {
                threadLocal.set(threadLocal.get() + 1);
                Thread.yield();
            }
            System.out.println(threadLocal.get());
        });
        Thread thread2 = new Thread(() -> {
            threadLocal.set(0);
            for (int i = 0; i < 100; i++) {
                threadLocal.set(threadLocal.get() + 1);
                Thread.yield();
            }
            System.out.println(threadLocal.get());
        });
        thread1.start();
        thread2.start();
    }

}

运行的结果

100
100

循环时增加yield()尽可能让thread1和thread2发生竞争。无论运行多少次,结果都是2个100不会变。由此可见,虽然两个线程实例共用同一个ThreadLocal实例,使用的Integer也不是线程安全类,但是没有出现资源抢占引起的数据错误。

源码解读

通过看源码来了解如何做到这一切的。

public T get() {
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null) {
		ThreadLocalMap.Entry e = map.getEntry(this);
		if (e != null) {
			@SuppressWarnings("unchecked")
			T result = (T)e.value;
			return result;
		}
	}
	return setInitialValue();
}
public void set(T value) {
	Thread t = Thread.currentThread();
	ThreadLocalMap map = getMap(t);
	if (map != null)
		map.set(this, value);
	else
		createMap(t, value);
}

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

ThreadLocal的get()和set()方法,首先会获取当前线程实例,然后读取当前线程的ThreadLocalMap实例,这是从Thread的私有属性threadLocals获取到的,不同实例不共享的,所以对各自的ThreadLocalMap操作也就不存在并发问题。
ThreadLocalMap是ThreadLocal的内部静态类,是由Entry数组实现的Map,但与常用的HashMap等Map不同的是,发生Hash冲突上的处理、和垃圾数据的清理。这一块可以后面再细说
ThreadLocalMap的key是ThreadLocal实例的引用,value是ThreadLocal类get()和set()方法操作的值。

static class ThreadLocalMap {

        /**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16;

        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        /**
         * The number of entries in the table.
         */
        private int size = 0;

        /**
         * The next size value at which to resize.
         */
        private int threshold; // Default to 0
...
}

如下图,梳理了Thread、ThreadLocal、ThreadLocalMap、Entry这几个主要的类之间的引用关系

每个线程持有的这份独立副本,只有在线程销毁时才会被GC清理,除非这份副本还被其他强引用关联。
上图中的虚线代表的是弱引用WeakReference。由图看出,在某一个Thread实例被销毁时,对应的ThreadLocalMap实例的key还在被ThreadLocal实例引用,是GCRoot可达的,如果此处是强引用,那就不会被GC回收,线程被多次创建销毁后,会出现OOM问题。
还有一个点需要注意,为了降低线程频繁创建销毁带来的性能开销,通常会使用线程池,这就意味着,线程在使用完后并不一定会被销毁,可能会被反复使用,那么线程的这份副本可能有脏读的现象,如果value是个集合类,可能还会出现value不断被添加元素导致内存占用不断变大直至OOM。所以在线程池中使用ThreadLocal在线程使用完后要及时去手动remove()清理。

标签:Thread,ThreadLocalMap,threadLocal,value,ThreadLocal,源码,线程
From: https://www.cnblogs.com/minisdad/p/17539519.html

相关文章

  • Qt源码阅读(五)-deleteLater
    QtdeleteLater作用及源码分析个人经验总结,如有错误或遗漏,欢迎各位大佬指正......
  • 所有源码关注公众号获取
    一、所有源码获取方法:1.关注公众号->商业项目->杂货铺->cv视觉源码;2.根据关键字搜索项目二、项目合作、学生毕设:1.专业的团队,985毕业,大厂工作;2.专业的服务,支持答疑;        公众号:                    个人号: ......
  • 基于MFC dll实现C++/CLI dll组件全过程详解(附完整源码) 浮云绘图
    ​模块化组件化实现独立的功能模块是软件设计的良好习惯,一般用实现为DLL。普通的DLL对外提供接口是采用导出函数接口,如果接口数量不大,只是50个以内,这种方式很适合;如果对外接口有上百个,导出函数接口就完全破坏了软件模块化分层设计的理念,使用接口非常麻烦,此情形采用C++/CLI导出类......
  • 透明信息提示框CFyToolTip设计及源码 适用于各类绘图的实时信息展示
    在图形绘制领域,经常需要用到透明的信息提示窗口,比如当鼠标移动到一个图元上,显示该图元对象的实时数据(如设备名称、状态、实测数据等),当鼠标移开,及时隐藏该提示框;比如在曲线控件绘图时,随着鼠标移动,实时展示曲线对应的横纵坐标值等​ 各种通用开发库里,也有类似的控件,如C#WinFor......
  • 多子曲线的曲线组件源码定制之详细功能需求,适用工控、军工、金融等数据分析领域 浮云E
    ​ 前文已经详细介绍了通用曲线控件源码定制开发从需求到编码实现,具体可参阅 通用曲线控件源码定制之设计实现篇 和 通用曲线控件定制之重点难点篇(附源码),本文由浮云E绘图开启大项目多曲线海量数据的曲线组件分析和实现之路。 一、需求背景在一些工业控制领域,有大量设备采......
  • 通用曲线控件定制之重点难点篇(附源码,功能丰富灵活) 浮云E绘图
    ​ 上篇已经介绍通用曲线控件源码定制之设计实现,详细描述了通用曲线控件的功能部件及其结构关系,并且实现了核心类的源码,本文由浮云E绘图继续介绍通用曲线控件定制开发的重点和难点,并附完整源码。 一.曲线控件源码类使用流程根据上文通用曲线控件源码定制之设计实现篇可知曲......
  • python 下载element源码
    #encoding:utf-8frombs4importBeautifulSoupimportrequests,re,os,socketfromurllibimportrequest#指定要下载的版本element_ui_version="2.15.13"#指定文件要存放的位置element_ui_dir="D:/tmp"save_ui_dir=os.path.join(element_ui_dir,"elem......
  • 分布式ID|从源码角度深度解析美团Leaf双Buffer优化方案
    分布式ID的使用场景基于MySql的初步方案第一次优化:Leaf-segment数据库方案第二次优化:Leaf-segment双buffer优化源码解析双buffer优化方案 背景 在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。如在美团点评的金融、支付、餐饮、酒店、猫眼电影等产......
  • 我用numpy实现了GPT-2,GPT-2源码,GPT-2模型加速推理,并且可以在树莓派上运行,读了不少hung
     之前分别用numpy实现了mlp,cnn,lstm和bert模型,这周顺带搞一下GPT-2,纯numpy实现,最重要的是可在树莓派上或其他不能安装pytorch的板子上运行,生成数据gpt-2的mask-multi-headed-self-attention我现在才彻底的明白它是真的牛逼,比bert的multi-headed-self-attention牛的不是一点半点,......
  • 【开源分享】在线客服系统源码,支持发送文本表情,上传图片附件附详细搭建教程
    源码介绍golang开发的单用户在线客服系统,功能非常的简洁实用,没有多余的功能。golang语言可编译为二进制程序,自带守护进程功能,相比于流传最广的PHP客服系统要稳定环境配置服务器:linux或者windows都可以golang运行环境MySQLNginx配置Golang环境Windows系统首先下载golang......