threadlocal get为空_面试常见知识点:ThreadLocal
老套路,先列举下关于ThreadLocal常见的疑问,希望可以通过这篇学习笔记来解决这几个问题:
- ThreadLocal是用来解决什么问题的?
- 如何使用ThreadLocal?
- ThreadLocal的实现原理是什么?
- 可否举几个实际项目中使用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变量实现:
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是业务上变量的值。
首先看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类,主要的成员和接口如下:
- 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)开源项目中有哪些案例可以参考?不知道你是否对这几个问题有了一定的了解呢?如果还有疑问,欢迎交流。
参考资料
- Why 0x61c88647?
- Java ThreadLocal
- When and how should I use a ThreadLocal variable?
- 技术小黑屋:理解Java中的ThreadLocal
- 深入分析ThreadLocal的内存泄漏问题
- 《Java并发编程实战》
- InheritableThreadLocal详解
- ThreadLocal详解
- ThreadLocal的使用场景
- 数据结构:哈希表
threadlocal get为空_面试常见知识点:ThreadLocal相关推荐
- Java面试常见知识点总结
目录 面试常见知识点 静态代码块 代码块 构造方法之间的顺序 interface和abstract的区别 abstract能不能继承interface 反正可不可以 interface 和abstra ...
- JAVA编程多线程面试常见知识点灵魂拷问(一)
1.对线程池的了解 ThreadPoolExcutor创建线程池,包含七个参数.七个参数分别是核心线程数.最大线程数.任务队列.等待时间.时间单位.线程工厂.拒绝策略. 任务加入线程池算法流程,若加入 ...
- sqlserver2008未将对象引用设置到对象的实例_面试官:ThreadLocal 的内存泄漏是弱引用导致的,你确定?...
面试官:ThreadLocal 了解吗? Python 小星:线程局部变量,多线程下能保证各个线程的变量相对独立于其他线程的变量. 面试官:那你说下它是如何保证线程隔离的? Python 小星:每个线 ...
- 弱引用使用场景桌面_面试|再次讲解Threadlocal使用及其内存溢出
ThreadLocal彻底详解 整理本文主要是想帮助大家完全消化面试中常见的ThreadLocal问题.希望读懂此文以后大家可以掌握: 简单介绍原理 ThreadLocal使用案例场景 Threadl ...
- python面试常见知识点收集,长期更新
问题 Python 1 python的数据类型 字典,字符串,列表,元组,集合 元组和列表的区别 元组和列表都属于序列. 列表属于可变序列,它的元素可以随时修改或者删除,而元组属于不可变序列,其中的元 ...
- 京东深资架构师告诉你Java面试常见知识点(建议收藏)
后端架构师 专注研究 Java 核心技术.架构,不限于分享算法.架构.高并发.多线程.JVM.Spring Boot.Maven.分布式.Spring Cloud +Docker+k8s.Dubbo. ...
- system流怎么判断为空_面试时被问到单例模式,怎么回答才能让面试官眼前一亮?...
考虑到马上到来的金九银十的面试季,我给大家介绍一下面试官的必问题,先从单例模式开始,这个设计模式看似简单,想回答得让面试官眼前一亮,还不是那么容易的. 一.什么是单例模式 单例模式是一种常用的软件设计 ...
- 计算机网络面试常见知识点(含HTTPS和TLS)
计算机网络面试常见问题 计算机网络面试常见问题 一.TCP/IP 各层的结构与功能, 都有哪些协议? 1.1Tcp/IP的五层协议 1.2应用层 1.3运输层 1.4网络层 1.5数据链路层 1.6物 ...
- springboot 不响应字段为空_面试官扎心一问:Tomcat 在 SpringBoot 中是如何启动的?...
作者:木木匠 http://my.oschina.net/luozhou/blog/3088908 前言 我们知道 SpringBoot 给我们带来了一个全新的开发体验,我们可以直接把 web 程序达 ...
最新文章
- task_struct源码
- 浅谈分布式计算的开发与实现
- linux+取消磁盘阵列,Linux下彻底关闭某个RAID磁盘阵列
- linux sort 命令详解
- 【转】[iOS] 关于 self = [super init];
- SecureCRT安装
- javaweb系统oracle锁表,oracle、websphere下获取数据库连接时出现死锁
- 网口压线顺序_网线水晶头接法
- C语言分数加减乘除化简操作集(含测试源码)
- 如何调整计算机显卡,教你n卡发挥最大性能,电脑怎么设置独立显卡-
- (Network Analysis)graph centrality measures
- 李国飞:全面反思腾讯的战略
- 特此感谢!酷睿12、希捷硬盘、机械键盘……免费送大家
- bellman_ford
- android+添加网络权限,已在AndroidManifest.xml增添网络权限,运行还报错
- 原生js实现轮盘抽奖,控制中奖概率(完整示例)
- HE网站系统架设过程思路
- 国内数字藏品的乱象与未来
- 做好如何融洽处理人际关系文章 把握是前言
- 关于Android Studio Arctic Fox 无法在 build.gradle(Project) 中添加第三方Maven仓库的解决方案
热门文章
- 搜索引擎下拉食云速捷详细_下拉框优化才云速捷一流!下拉框优化虑云速捷豪杰...
- 这个开源数据集要在全球扩大中文NLP影响力,你也能来做贡献!
- EMNLP'20最佳论文揭晓!八块肌肉=能打电话?!
- Spring Boot中使用@Async实现异步调用
- 在传统Spring应用中使用spring-boot-actuator模块提供监控端点
- Spring Cloud构建微服务架构:服务网关(基础)【Dalston版】
- Session-basedRecommendationwithGraphNeuralNetworks(论文笔记)
- Pandas中的元素替换
- 因果关系固定搭配知识库项目CausalCollocation
- 中文词语概念上下位图谱项目