定义

ThreadLocal是线程局部变量,不同线程的threadlocal相互独立。它是一种保存线程私有信息的机制,因为在现成的整个生命周期都有效,

所以可以方便地在一个线程关联的不同业务模块之间传递信息,比如事务ID、Cookie等上下文相关信息。

特点:

  • 简单,开箱即用。
  • 快速,无额外开销。
  • 安全,线程安全。

threadlocal设计实现的优点:开箱即用,代码易读,符合经常阅读偶尔使用的原则

场景:资源持有、线程一致性、并发计算等多线程场景。

API

场景分析

如jdbc事务的请求,part i 代表不同事务,如更新用户信息、更新订单信息等,需要保证各事务资源的一致性。

每一个事务请求连接时,先去threadlocal map里找,如果不存在,到连接池中请求分配连接,并存入map。

总结,ThreadLocal的主要作用是:

  • 持有线程资源,供线程的各个部分使用
  • 帮助维护多线程共享资源的一致性
  • 线程安全的一种方案

场景实验,观察Spring框架在多线程场景的执行情况

压力测试工具,apache2-util

10000此请求,单线程

10000次请求,线程数加到100



虽然完成10000请求的速度变快了,但最终curl请求get到的数据会不一致。

原因是,多线程下,c是临界资源,c=c+1不具备原子性,要先读取c,在执行加1,再执行赋值。

对c的访问加锁



测试发现curl get到的结果是10000,但速度会很慢,原因是加锁导致排队,并发实质上变成了串行,性能被锁卡住。

解决方法就是使用ThreadLocal,让线程在自己的局部变量资源上运行。

把c设为ThreadLocal



测试发现,最终统计数据依然不准确。



原因是spring默认线程池有20多个线程,这些线程每一个都有自己的局部变量c,执行10000请求后,需要收集各个线程的数据。

收集多个ThreadLocal中的数据

虽然threadlocal是各个线程独占的数据,但也是进程持有的,不过java没有提供收集数据的接口,所以可以通过hashmap或

hashset来存储threadlocal,最后一并收集。







改进,因为set访问需要同步,所以addset中加入同步锁,而且set访问次数最多是线程池的线程数,相对c的访问次数要少,

属于低频访问,所以对总体性能影响小。

实验总结

  • 基于线程池模型加同步锁很危险,可能因排队等锁,导致cpu使用不充分,从而严重拖慢性能。
  • 虽然多线程不能完全避免同步问题,但使用ThreadLocal,可以把高频同步化为低频同步。

实现原理

ThreadLocalMap

自定义一个类似HashMap的ThreadLocalMap存放ThreadLocal(弱引用),该map时ThreadLocal的静态内部类,延续了lazy-load模式,初始容量16,门限2/3。

ThreadLocalMap里面有个Entry数组,只有数组没有像HashMap那样有链表,因此当hash冲突的之后,ThreadLocalMap是采用线性探测的方式解决hash冲突。

Entry把ThreadLocal的弱引用作为key,让没用的ThreadLocal被GC清除。

ThreadLocal内部没有存储任何的值,它的作用只是当我们的ThreadLocalMap的key,让线程可以拿到对应的value。

satic class ThreadLocalMap {
satic class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
// …
}

回收被内存回收的弱引用所占的槽是超级复杂问题。

Key为null时,该条目就变成“废弃条目”,相关“value”的回收,

往往依赖于几个关键点,即set、remove、rehash。当调用get()、set()方法时会去找到那个key被干掉的entry然后干掉它。

并且提供了remove()方法。虽然get()、set()会清理key为null的Entry,但是不是每次调用就会清理的,只有当get时候

直接hash没中,或者set时候也是直接hash没中,开始线性探测时候,碰到key为null的才会清理。

下面set的精简代码,具体的清理逻辑是实现在cleanSomeSlots和expungeStaleEntry之中。

private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];; …) {
//…
if (k == null) {
// 替换废弃条目
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
// 扫描并清理发现的废弃条目,并检查容量是否超限
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();// 清理废弃条目,如果仍然超限,则扩容(加倍)
}

废弃项目的回收依赖于显式地触发,否则就要等待线程结束,进而回收相应ThreadLocalMap!这就是很多OOM的来源,

所以应用一定要自己负责remove,并且不要和线程池配合,因为worker线程往往是不会退出的。

hash算法

散列更均匀,减少冲突

解决冲突的方法是线形探测。这种方式解决hash冲突的效率很低,因此要注意ThreadLocal的数量。

线性探测,就是先根据初始key的hashcode值确定元素在table数组中的位置,如果这个位置上已经有其他key值的元素被占用,

则利用固定的算法寻找一定步长的下个位置,依次直至找到能够存放的位置。在ThreadLocalMap步长是1。

总结

解决一致性问题,除了排队(加锁)、投票(拜占庭将军)、CAS+voilate外,ThreadLocal不失为一个更轻量级的优选方案。

参考

https://www.imooc.com/learn/1217

《深入理解Java虚拟机》周志明

《Java核心技术36讲》杨晓峰

对ThreadLocal的理解

深入理解Java多线程——ThreadLocal的更多相关文章

  1. Java多线程——ThreadLocal类的原理和使用

    Java多线程——ThreadLocal类的原理和使用 摘要:本文主要学习了ThreadLocal类的原理和使用. 概述 是什么 ThreadLocal可以用来维护一个变量,提供了一个ThreadLo ...

  2. [Java多线程]-ThreadLocal源码及原理的深入分析

    ThreadLocal<T>类:以空间换时间提供一种多线程更快捷访问变量的方式.这种方式不存在竞争,所以也不存在并发的安全性问题. //-------------------------- ...

  3. Java多线程ThreadLocal介绍

    在Java多线程环境下ThreadLocal就像一家银行,每个线程就是银行里面的一个客户,每个客户独有一个保险箱来存放金钱,客户之间的金钱不影响. private static ThreadLocal ...

  4. Java多线程——ThreadLocal类

    一.概述   ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是threadlocalvariable(线程局部变量).也许把它命名 ...

  5. 深入理解Java多线程——线程池

    目录 为什么需要线程池 定义 ThreadPoolExecutor 工作队列workQueue 不同的线程池 Executor 线程池的工作原理 线程池生命周期 线程池增长策略 线程池大小的设置 线程 ...

  6. 并发编程之美,带你深入理解java多线程原理

    1.什么是多线程? 多线程是为了使得多个线程并行的工作以完成多项任务,以提高系统的效率.线程是在同一时间需要完成多项任务的时候被实现的. 2.了解多线程 了解多线程之前我们先搞清楚几个重要的概念! 如 ...

  7. 深入理解 Java 多线程核心知识

    多线程相对于其他 Java 知识点来讲,有一定的学习门槛,并且了解起来比较费劲.在平时工作中如若使用不当会出现数据错乱.执行效率低(还不如单线程去运行)或者死锁程序挂掉等等问题,所以掌握了解多线程至关 ...

  8. 2018跳槽面试必备之深入理解 Java 多线程核心知识

    导语:多线程相对于其他 Java 知识点来讲,有一定的学习门槛,并且了解起来比较费劲.在平时工作中如若使用不当会出现数据错乱.执行效率低(还不如单线程去运行)或者死锁程序挂掉等等问题,所以掌握了解多线 ...

  9. 深入理解 Java 多线程核心知识:跳槽面试必备

    多线程相对于其他 Java 知识点来讲,有一定的学习门槛,并且了解起来比较费劲.在平时工作中如若使用不当会出现数据错乱.执行效率低(还不如单线程去运行)或者死锁程序挂掉等等问题,所以掌握了解多线程至关 ...

随机推荐

  1. GO语言复合类型01---指针

    package main /* %T 类型占位符 %v 值占位符 %p 地址(指针)占位符,只有地址才能替换%p &value 对值取地址 *addr 对地址取值 **int 指向int型指针 ...

  2. eclipse集成processing、PApplet、proclipsing 问题

    最近老是换应用平台,将processing里的代码转移到eclipse中. processing 关于转换成eclipse的介绍也可以使用,但是没有介绍具体怎么使用第三方库 Processing in ...

  3. 深度学习与TensorFlow

    深度学习与TensorFlow DNN(深度神经网络算法)现在是AI社区的流行词.最近,DNN 在许多数据科学竞赛/Kaggle 竞赛中获得了多次冠军. 自从 1962 年 Rosenblat 提出感 ...

  4. C++标准模板库(STL)——set常见用法详解

    set的定义 set<typename> name; typename可以是任何基本类型,如int.double.char.结构体等,也可以是STL标准容器,如vector.set.que ...

  5. Java 面试题关于包装类

    这几个问题的知识点涉及的内容非常的刁钻,值得自己好好的理解. 问以下程序的输出结果是: 问题一: Object object=true ? new Integer(1):new Double(2.0) ...

  6. 【Android编程实战】源码级免杀_Dex动态加载技术_Metasploit安卓载荷傀儡机代码复现

    /文章作者:MG193.7 CNBLOG博客ID:ALDYS4 QQ:3496925334/ 在读者阅读本文章前,建议先阅读笔者之前写的一篇对安卓载荷的分析文章 [逆向&编程实战]Metasp ...

  7. 实验3、Flask数据库操作-如何使用Flask与数据库

    1. 实验内容 数据库的使用对于可交互的Web应用程序是极其重要的,本节我们主要学习如何与各种主要数据库进行连接和使用,以及ORM的使用 2. 实验要点 掌握Flask对于各种主要数据库的连接方法 掌 ...

  8. Spring WebFlux 教程:如何构建反应式 Web 应用程序

    Spring WebFlux 教程:如何构建反应式 Web 应用程序 反应式系统提供了我们在高数据流世界中所需的无与伦比的响应能力和可扩展性.然而,反应式系统需要经过专门培训的工具和开发人员来实现这些 ...

  9. 感知机与支持向量机 (SVM)

    感知机与SVM一样都是使用超平面对空间线性可分的向量进行分类,不同的是:感知机的目标是尽可能将所有样本分类正确,这种策略指导下得出的超平面可能有无数个,然而SVM不仅需要将样本分类正确,还需要最大化最 ...

  10. php 安装 yii 报错: phpunit/phpunit 4.8.32 requires ext-dom *

    php 安装 yii 报错: phpunit/phpunit 4.8.32 requires ext-dom * 我的版本是7.0,以7.0为例演示. 先装这两个拓展试试: sudo apt-get ...