threadlocal get为空_面试常见知识点:ThreadLocal

f503ac075cc3f415ed27b7896ef3bff2.png

老套路,先列举下关于ThreadLocal常见的疑问,希望可以通过这篇学习笔记来解决这几个问题:

  1. ThreadLocal是用来解决什么问题的?
  2. 如何使用ThreadLocal?
  3. ThreadLocal的实现原理是什么?
  4. 可否举几个实际项目中使用ThreadLocal的案例?

基础知识

ThreadLocal是线程局部变量,和普通变量的不同在于:每个线程持有这个变量的一个副本,可以独立修改(set方法)和访问(get方法)这个变量,并且线程之间不会发生冲突。

类中定义的ThreadLocal实例一般会被private static修饰,这样可以让ThreadLocal实例的状态和Thread绑定在一起,业务上,一般用ThreadLocal包装一些业务ID(user ID或事务ID)——不同的线程使用的ID是不相同的。

如何使用

case1

从某个角度来看,ThreadLocal为Java并发编程提供了额外的思路——避免并发,如果某个对象本身是非线程安全的,但是你想实现多线程同步访问的效果,例如SimpleDateFormat,你可以使用ThreadLocal变量。

public class Foo{ // SimpleDateFormat is not thread-safe, so give one to each thread private static final ThreadLocal formatter = new ThreadLocal(){ @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyyMMdd HHmm"); } };​ public String formatIt(Date date) { return formatter.get().format(date); }}

注意,这里针对每个线程只需要初始化一次SimpleDateFormat对象,其实跟在自定义线程中定义一个SimpleDateFormat成员变量,并在线程初始化的时候new这个对象,效果是一样的,只是这样看起来代码更规整。

case2

之前在yunos做酷盘项目的数据迁移时,我们需要按照用户维度去加锁,每个线程在处理迁移之前,都需要先获取当前用户的锁,每个锁的key是带着用户信息的,因此也可以使用ThreadLocal变量实现:

752011ecc8e3e8521c03dcd597050900.png

case3

下面这个例子,我们定义了一个MyRunnable对象,这个MyRunnable对象会被线程1和线程2使用,但是通过内部的ThreadLocal变量,每个线程访问到的整数都是自己单独的一份。

package org.java.learn.concurrent.threadlocal;​/** * @author duqi * @createTime 2018-12-29 23:25 **/public class ThreadLocalExample { public static class MyRunnable implements Runnable {​ private ThreadLocal threadLocal = new ThreadLocal();​ @Override public void run() { threadLocal.set((int) (Math.random() * 100D));​ try { Thread.sleep(2000); } catch (InterruptedException e) { }​ System.out.println(threadLocal.get()); } }​​ public static void main(String[] args) throws InterruptedException { MyRunnable sharedRunnableInstance = new MyRunnable();​ Thread thread1 = new Thread(sharedRunnableInstance); Thread thread2 = new Thread(sharedRunnableInstance);​ thread1.start(); thread2.start();​ thread1.join(); //wait for thread 1 to terminate thread2.join(); //wait for thread 2 to terminate }}

ThreadLocal关键知识点

源码分析

ThreadLocal是如何被线程使用的?原理如下图所示:Thread引用和ThreadLocal引用都在栈上,Thread引用会引用一个ThreadLocalMap对象,这个map中的key是ThreadLocal对象(使用WeakReference包装),value是业务上变量的值。

99337fa348c97bc2d359b1e331656119.png

首先看java.lang.Thread中的代码:

publicclass Thread implements Runnable { //......其他源码 /* 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中的threadLocals变量指向的是一个map,这个map就是ThreadLocal.ThreadLocalMap,里面存放的是跟当前线程绑定的ThreadLocal变量;inheritableThreadLocals的作用相同,里面也是存放的ThreadLocal变量,但是存放的是从当前线程的父线程继承过来的ThreadLocal变量。

在看java.lang.ThreadLocal类,主要的成员和接口如下:

3d1dd8552ff0b395385c1b523e946e9a.png
  • withInitial方法,Java 8以后用于初始化ThreadLocal的一种方法,在外部调用get()方法的时候,会通过Supplier确定变量的初始值;
public static  ThreadLocal withInitial(Supplier extends S> supplier) { return new SuppliedThreadLocal<>(supplier);}
  • get方法,获取当前线程的变量副本,如果当前线程还没有创建该变量的副本,则需要通过调用initialValue方法来设置初始值;get方法的源代码如下,首先通过当前线程获取当前线程对应的map,如果map不为空,则从map中取出对应的Entry,然后取出对应的值;如果map为空,则调用setInitialValue设置初始值;如果map不为空,当前ThreadLocal实例对应的Entry为空,则也需要设置初始值。
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();}
  • set方法,跟get方法一样,先获取当前线程对应的map,如果map为空,则调用createMap创建map,否则将变量的值放入map——key为当前这个ThreadLocal对象,value为变量的值。
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value);}
  • remove方法,删除当前线程绑定的这个副本
 public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); }
  • 数字0x61c88647,这个值是HASH_INCREMENT的值,普通的hashmap是使用链表来处理冲突的,但是ThreadLocalMap是使用线性探测法来处理冲突的,HASH_INCREMENT就是每次增加的步长,根据参考资料1所说,选择这个数字是为了让冲突概率最小。
 /** * The difference between successively generated hash codes - turns * implicit sequential thread-local IDs into near-optimally spread * multiplicative hash values for power-of-two-sized tables. */ private static final int HASH_INCREMENT = 0x61c88647;

父子进程数据共享

InheritableThreadLocal主要用于子线程创建时,需要自动继承父线程的ThreadLocal变量,实现子线程访问父线程的threadlocal变量。InheritableThreadLocal继承了ThreadLocal,并重写了childValue、getMap、createMap三个方法。

public class InheritableThreadLocal extends ThreadLocal { /** * 创建线程的时候,如果需要继承且父线程中Thread-Local变量,则需要将父线程中的ThreadLocal变量一次拷贝过来。 */ protected T childValue(T parentValue) { return parentValue; }​ /** * 由于重写了getMap,所以在操作InheritableThreadLocal变量的时候,将只操作Thread类中的inheritableThreadLocals变量,与threadLocals变量没有关系 **/ ThreadLocalMap getMap(Thread t) { return t.inheritableThreadLocals; }​ /** * 跟getMap类似,set或getInheritableThreadLocal变量的时候,将只操作Thread类中的inheritableThreadLocals变量 */ void createMap(Thread t, T firstValue) { t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue); }}

关于childValue多说两句,拷贝是如何发生的?

首先看Thread.init方法,

 private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { //其他源码 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize;​ /* Set thread ID */ tid = nextThreadID(); }

然后看ThreadLocal.createInheritedMap方法,最终会调用到newThreadLocalMap方法,这里InheritableThreadLocal对childValue做了重写,可以看出,这里确实是将父线程关联的ThreadLocalMap中的内容依次拷贝到子线程的ThreadLocalMap中了。

 private ThreadLocalMap(ThreadLocalMap parentMap) { Entry[] parentTable = parentMap.table; int len = parentTable.length; setThreshold(len); table = new Entry[len];​ for (int j = 0; j < len; j++) { Entry e = parentTable[j]; if (e != null) { @SuppressWarnings("unchecked") ThreadLocal key = (ThreadLocal) e.get(); if (key != null) { Object value = key.childValue(e.value); Entry c = new Entry(key, value); int h = key.threadLocalHashCode & (len - 1); while (table[h] != null) h = nextIndex(h, len); table[h] = c; size++; } } } }

ThreadLocal对象何时被回收?

ThreadLocalMap中的key是ThreadLocal对象,然后ThreadLocal对象时被WeakReference包装的,这样当没有强引用指向该ThreadLocal对象之后,或者说Map中的ThreadLocal对象被判定为弱引用可达时,就会在垃圾收集中被回收掉。看下Entry的定义:

 static class Entry extends WeakReference> { /** The value associated with this ThreadLocal. */ Object value;​ Entry(ThreadLocal> k, Object v) { super(k); value = v; }}

ThreadLocal和线程池一起使用?

ThreadLocal对象的生命周期跟线程的生命周期一样长,那么如果将ThreadLocal对象和线程池一起使用,就可能会遇到这种情况:一个线程的ThreadLocal对象会和其他线程的ThreadLocal对象串掉,一般不建议将两者一起使用。

案例学习

Dubbo中对ThreadLocal的使用

我从Dubbo中找到了ThreadLocal的例子,它主要是用在请求缓存的场景,具体代码如下:

@Activate(group = {Constants.CONSUMER, Constants.PROVIDER}, value = Constants.CACHE_KEY)public class CacheFilter implements Filter {​ private CacheFactory cacheFactory;​ public void setCacheFactory(CacheFactory cacheFactory) { this.cacheFactory = cacheFactory; }​ @Override public Result invoke(Invoker> invoker, Invocation invocation) throws RpcException { if (cacheFactory != null && ConfigUtils.isNotEmpty(invoker.getUrl().getMethodParameter(invocation.getMethodName(), Constants.CACHE_KEY))) { Cache cache = cacheFactory.getCache(invoker.getUrl(), invocation); if (cache != null) { String key = StringUtils.toArgumentString(invocation.getArguments()); Object value = cache.get(key); if (value != null) { if (value instanceof ValueWrapper) { return new RpcResult(((ValueWrapper)value).get()); } else { return new RpcResult(value); } } Result result = invoker.invoke(invocation); if (!result.hasException()) { cache.put(key, new ValueWrapper(result.getValue())); } return result; } } return invoker.invoke(invocation); }

可以看出,在RPC调用(invoke)的链路上,会先使用请求参数判断当前线程是否刚刚发起过同样参数的调用——这个调用会使用ThreadLocalCache保存起来。具体的看,ThreadLocalCache的实现如下:

package org.apache.dubbo.cache.support.threadlocal;​import org.apache.dubbo.cache.Cache;import org.apache.dubbo.common.URL;​import java.util.HashMap;import java.util.Map;​/** * ThreadLocalCache */public class ThreadLocalCache implements Cache {​ //ThreadLocal里存放的是参数到结果的映射 private final ThreadLocal> store;​ public ThreadLocalCache(URL url) { this.store = new ThreadLocal>() { @Override protected Map initialValue() { return new HashMap(); } }; }​ @Override public void put(Object key, Object value) { store.get().put(key, value); }​ @Override public Object get(Object key) { return store.get().get(key); }​}

RocketMQ

在RocketMQ中,我也找到了ThreadLocal的身影,它是用在消息发送的场景,MQClientAPIImpl是RMQ中负责将消息发送到服务端的实现,其中有一个步骤需要选择一个具体的队列,选择具体的队列的时候,不同的线程有自己负责的index值,这里使用了ThreadLocal的机制,可以看下ThreadLocalIndex的实现:

package org.apache.rocketmq.client.common;​import java.util.Random;​public class ThreadLocalIndex { private final ThreadLocal threadLocalIndex = new ThreadLocal(); private final Random random = new Random();​ public int getAndIncrement() { Integer index = this.threadLocalIndex.get(); if (null == index) { index = Math.abs(random.nextInt()); if (index < 0) index = 0; this.threadLocalIndex.set(index); }​ index = Math.abs(index + 1); if (index < 0) index = 0;​ this.threadLocalIndex.set(index); return index; }​ @Override public String toString() { return "ThreadLocalIndex{" + "threadLocalIndex=" + threadLocalIndex.get() + '}'; }}

总结

这篇文章主要是解决了关于ThreadLocal的几个问题:(1)具体的概念是啥?(2)在Java开发中的什么场景下使用?(3)ThreadLocal的实现原理是怎样的?(4)开源项目中有哪些案例可以参考?不知道你是否对这几个问题有了一定的了解呢?如果还有疑问,欢迎交流。

参考资料

  1. Why 0x61c88647?
  2. Java ThreadLocal
  3. When and how should I use a ThreadLocal variable?
  4. 技术小黑屋:理解Java中的ThreadLocal
  5. 深入分析ThreadLocal的内存泄漏问题
  6. 《Java并发编程实战》
  7. InheritableThreadLocal详解
  8. ThreadLocal详解
  9. ThreadLocal的使用场景
  10. 数据结构:哈希表

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/490221.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

行业观察 | 新一轮AI周期里,华为拿什么破解核心难题?

来源&#xff1a;财经杂志第三方研究机构OpenAI近期发布的研究显示&#xff0c;仅2012年以来&#xff0c;人们对于算力的需求增长六年就超过30万倍&#xff0c;平均每年增长10倍&#xff0c;远超摩尔定律的发展速度。面对AI算力需求的爆发式增长&#xff0c;华为作为AI的后入局…

swot分析法案例_(附数据集)SWOT分析实战案例!

最近一直在为一件事苦恼&#xff0c;开在市中心的那家西点店的生意越做越好&#xff0c;分店也开了两家&#xff0c;但挣得这点钱还是不够买房子。这不&#xff0c;最近看上了这如火如荼的奶茶市场&#xff0c;想借现有资源开一家奶茶店&#xff0c;但不知道这个想法靠不靠谱&a…

人工智能、核聚变、碳捕捉……最有可能帮助拯救地球的10项技术

来源&#xff1a; 资本实验室亚马逊森林大火、加速消逝的北极冰面、有记录以来最热的7月、越来越多的极端天气事件……各种因素正在让地球、让我们的生存环境经受日趋严重的考验。大问题往往意味着大机会。在人类对环境制造麻烦的过程中&#xff0c;势必又不断催生出各种新技术…

点击按钮弹出iframe_WEB安全(四) :CSRF与点击劫持

跨站点请求伪造CSRF是跨站点请求伪造&#xff0c;它的请求有两个关键点&#xff0c;跨站点的请求与请求是伪造的&#xff0c;从字面上看&#xff0c;跨站点的请求来源应该是其他站点&#xff0c;比如&#xff0c;目标网站接收到来源网站的恶意操作&#xff0c;但是&#xff0c;…

两种超级智能,哪一个对人类未来影响更为深远?

来源&#xff1a;刘锋科学网博客前言&#xff1a;无论在学术界还是产业界&#xff0c;超级智能并没有被纳入正式的体系概念中&#xff0c;因此超级智能究竟是什么&#xff0c;也没有统一的的定义&#xff0c;事实上在过去的时间里&#xff0c;出现了两种不同的超级智能概念&…

Java9都快发布了,Java8的十大新特性你了解多少呢?

Java 9预计将于今年9月份发布&#xff0c;这是否会是一次里程碑式的版本&#xff0c;我们拭目以待。今天&#xff0c;我们先来复习一下2014年发布的Java 8的十大新特性。 Java 8可谓是自Java 5以来最具革命性的版本了&#xff0c;她在语言、编译器、类库、开发工具以及Java虚拟…

struts2 redirect 获取参数null_社会化登录支持授权域以便于获取更大权限

使用 Authing 可以轻松接入许多社会化登录&#xff0c;同时无需翻看社会化登录提供方的繁琐接入文档&#xff0c;让开发者更加专注业务从而提升开发效率。这看起来很方便&#xff0c;But&#xff0c;通过 Authing 登录的社会化登录用户&#xff0c;无法获取更高级的权限&#x…

美国科技界高度关注中国科技创新进展

来源&#xff1a;新华网近日&#xff0c;美国《福布斯》双周刊网站刊文说&#xff0c;中国互联网和高科技企业已超越模仿时代&#xff0c;成为投资和研发的全球领先者&#xff0c;中国的科技巨头进入了全新的创新时代。记者在采访中发现&#xff0c;美国科技界高度关注中国科技…

科技创新2030—“新一代人工智能”重大项目拟立项的2018年度项目公示清单

来源&#xff1a;财政资金申请根据《国务院关于改进加强中央财政科研项目和资金管理的若干意见》&#xff08;国发[2014]11号&#xff09;、《国务院关于深化中央财政科技计划&#xff08;专项、基金等&#xff09;管理改革方案的通知》&#xff08;国发[2014]64号&#xff09;…

SpringMVC核心——视图渲染(包含视图解析)问题

一、本来想说的是返回值处理问题&#xff0c;但在 SpringMVC 中&#xff0c;返回值处理问题的核心就是视图渲染。所以这里标题叫视图渲染问题。 本来想在上一篇文章中对视图解析进行说明的&#xff0c;但是通过源码发现&#xff0c;它应该算到视图渲染中&#xff0c;所以在这篇…

国际机器人联合会:全球工业机器人2019报告

来源&#xff1a;资本实验室据国际机器人联合会&#xff08;IFR&#xff09;最新发布的《全球机器人2019——工业机器人》报告数据&#xff0c;2018年全球工业机器人出货量42.2万台&#xff0c;比上年增长6%&#xff1b;年销售额达到165亿美元&#xff0c;创下新纪录。 IFR预测…

mediarecorder添加时间戳_Python脚本实现数据处理(官方实例)和Hive自带时间函数...

官网示例官网地址&#xff1a;https://cwiki.apache.org/confluence/display/Hive/GettingStarted#GettingStarted-ApacheWeblogData下载数据wget http://files.grouplens.org/datasets/movielens/ml-100k.zip我们下载好数据安装unzip yum install -y unzip解压数据创建库创建表…

从空城计到阿尔法狗,博弈论如何渗透我们的生活?

来源&#xff1a;返朴2016年&#xff0c;有那么一只“狗”大闹天宫&#xff0c;这在整个人类社会引起了轩然大波。它就是阿尔法狗&#xff08;AlphaGo&#xff09;&#xff0c;是谷歌&#xff08;Google&#xff09;旗下公司DeepMind 在人工智能与博弈论交叉研究上的一个杰作。…

反弹式木马原理_汽车避震器的原理与改装问题

一、避震器or避振器“震”字是指车轮在路面的滚动过程中&#xff0c;由于路面的不平&#xff0c;从路面传递上来的震动&#xff0c;而不是人为制造“振”动&#xff0c;所以使用“震”字更科学。二、避震器的工作原理避震器的工作原理是通过活塞运动产生阻尼力&#xff0c;将动…

科学探索奖首批50名获奖者都有谁?

来源&#xff1a;知识分子9月20日上午&#xff0c;经过四个多月的评审&#xff0c;2019年 获奖名单正式公布。来自全国26个科研单位、高校和企业的50位科学家成为首届“科学探索奖”获奖者&#xff0c;每人将在未来5年获得由腾讯基金会资助的300万元人民币。据悉&#xff0c;20…

untitled软件怎么用_苹果手机怎么用4G网络于App Store下载超过200MB以上的软件

近两年来&#xff0c;随着苹果中国市场份额被国产品牌挤占了不少&#xff0c;他们越来越懂得“聆听”中国用家的需要&#xff0c;其中一项便是用手机网络(4G)下载iOS应用。在这一点上我们中国人也不用再妄自菲薄了&#xff0c;在疯狂的4G建网以后&#xff0c;我们国家已经拥有全…

关于人脸识别的最全研究!

来源&#xff1a;北京物联网智能技术应用协会本文内容涵盖人脸识别发展历程、市场研究、核心技术、商业应用以及产业落地、个人看法等干货研究。注意&#xff0c;本文干货满满&#xff0c;约有2万7千字&#xff0c;强烈建议大家先收藏后学习&#xff01;01 发展史1. 人脸识别的…

关于字节对齐以及内存占用

参考博文&#xff1a; http://www.javamex.com/tutorials/memory/object_memory_usage.shtml 本文主要考虑正常情况下一个对象在堆上的内存占用情况&#xff1a;对于下面的特殊情况不作讨论 1、某些情况下&#xff0c;JVM可能不会把对象存储在堆上&#xff1a;比如小的线程私有…

人工智能应用实践与趋势

来源&#xff1a;阿里云研究中心《崛起的超级智能》一书主要阐述当今天人类为人工智能的春天到来而兴奋&#xff0c;为人工智能是否超越人类而恐慌的时候&#xff0c;一个更为庞大、远超人类预期的智能形态正在崛起&#xff0c;种种迹象表明50年来&#xff0c;互联网正在从网状…

excel表格打印每页都有表头_这么漂亮的Excel表格,用黑白打印机打印真是可惜了...

我们在日常工作中&#xff0c;利用Excel制作的表格通过会填充颜色&#xff0c;设置字体和边框格式、颜色&#xff0c;把表格弄的漂漂亮亮的&#xff0c;一个是为了缓解视觉疲劳&#xff0c;另一个是为了老板看到漂亮的表格一不小心加工资呢&#xff0c;但是一个问题出来了&…