ThreadLocal以及增强
多线程的本质就是增加任务的并发,提高效率。但是又要控制任务不错乱,可以通过锁来控制资源的访问。
除了控制资源的访问外,我们可以通过增加资源来保证所有对象的线程安全。比如100个人填写个人信息表,如果只有一支笔,那么大家都得排队,如果准备100支笔,这样人手一支笔,就可以很快完成填写信息。
如果说锁是第一种思路,ThreadLocal就是第二种思路。
ThreadLocal
ThreadLocal的简单示例
从ThreadLocal的名字上可以看到,这是一个线程的局部变量,也就是说只有当前线程可以访问,自然是线程安全的。
下面来看一个简单示例:
package main.java.study;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadLocalTest {private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");public static class ParseDate implements Runnable {int i = 0;public ParseDate(int i) {this.i = i;}public void run() {try {Date t = sdf.parse("2019-05-24 17:00:" + i % 60);System.out.println(i + ":" + t);} catch (ParseException e) {e.printStackTrace();}}}public static void main(String[] args) {// TODO Auto-generated method stubExecutorService es = Executors.newFixedThreadPool(10);for (int i = 0; i < 100; i++) {es.execute(new ParseDate(i));}}}
运行结果:
结果中即有正确的,又有错误的异常。出现这种问题的原因是SimpleDateFormat.parse()方法并不是线程安全的。因此在线程池中共享这个对象必然导致错误。
一种可行的方法是在sdf.parse()方法上加锁,这是一般思路,这里我们不这么做,我们使用ThreadLocal为每个线程都产生一个SimpleDateFormat对象。
package main.java.study;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadLocalTest2 {
static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();
public static class ParseDate implements Runnable {
int i = 0;public ParseDate(int i) {
this.i = i;
}public void run() {
try {
if (tl.get() == null) {
tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); //必须为ThreadLocal分配不同的对象,不然不能保证线程安全。
}
Date t = tl.get().parse("2019-05-24 17:00:" + i % 60);
System.out.println(i + ":" + t);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
ExecutorService es = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
es.execute(new ParseDate(i));
}
}}
执行结果:
从上面可以看出,为每个线程人手分配一个对象工作并不是由ThreadLoca来完成,而是在应用层保证。如果在应用上为每个线程分配了同一个对象,则ThreadLocal也不能保证线程安全。
ThreadLocal原理
(上图来源:https://blog.csdn.net/aaronsimon/article/details/82711336)
- 每个Thread线程内部都有一个Map;
- Map里面存储线程本地对象(key)和线程的变量副本(value)
- Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值。
所以对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离,互不干扰。
ThreadLocal源码
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();
}private T setInitialValue() {
T value = initialValue(); //子类可以覆盖的。
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return 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);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}ThreadLocal.ThreadLocalMap threadLocals = null;
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}//每个ThreadLocal对象都有一个HashCode
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
Thread.ThreadLocalMap<ThreadLocal, Object>;
1、Thread: 当前线程,可以通过Thread.currentThread()获取。
2、ThreadLocal:我们的static ThreadLocal变量。
3、Object: 当前线程共享变量。
我们调用ThreadLocal.get方法时,实际上是从当前线程中获取ThreadLocalMap<ThreadLocal, Object>,然后根据当前ThreadLocal获取当前线程共享变量Object。
ThreadLocal.set,ThreadLocal.remove实际上是同样的道理。
这种存储结构的好处:
1、线程死去的时候,线程共享变量ThreadLocalMap则销毁。
2、ThreadLocalMap<ThreadLocal,Object>键值对数量为ThreadLocal的数量,一般来说ThreadLocal数量很少,相比在ThreadLocal中用Map<Thread, Object>键值对存储线程共享变量(Thread数量一般来说比ThreadLocal数量多),性能提高很多。
关于ThreadLocalMap<ThreadLocal, Object>弱引用问题:
当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在ThreadLocalMap<null, Object>的键值对,造成内存泄露。(ThreadLocal被回收,ThreadLocal关联的线程共享变量还存在)。
虽然ThreadLocal的get,set方法可以清除ThreadLocalMap中key为null的value,但是get,set方法在内存泄露后并不会必然调用,所以为了防止此类情况的出现,有两种手段。
1、使用完线程共享变量后,显示调用ThreadLocalMap.remove方法清除线程共享变量;
2、JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。
InheritableThreadLocal
ThreadLocal大部分情况下均能正常work。但是,在当下互联网环境下,经常会用到了异步方式来提高程序运行效率。当时当在主线程设置ThreadLocal变量,在子线程get TheadLocal变量时,未能获取到正确值。这是因为子线程与主线程不是同一个线程,因此获取不到主线程设置的变量值。
JDK扩展了ThreadLocal,实现了一个子类InheritableThreadLocal,它能够向子线程传递数据。
public class InheritableThreadLocal<T> extends ThreadLocal<T> {protected T childValue(T parentValue) {return parentValue;}ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}void createMap(Thread t, T firstValue) {t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);}
}
InheritableTheadLocal主要复写了getMap,createMap2个方法。操作的属性为Thread的inheritableThreadLocals属性。
ThreadLocal.ThreadLocalMap threadLocals = null;ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
Thread类在构造时,会调用init方法
public Thread(Runnable target) {init(null, target, "Thread-" + nextThreadNum(), 0);}private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {if (name == null) {throw new NullPointerException("name cannot be null");}this.name = name;//当前线程为子线程的父线程Thread parent = currentThread();。。。。。。//调用ThreadLocal的createInheritedMap 方法if (inheritThreadLocals && parent.inheritableThreadLocals != null)this.inheritableThreadLocals =ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);/* Set thread ID */tid = nextThreadID();}
createInheritedMap
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {return new ThreadLocalMap(parentMap);}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<Object> key = (ThreadLocal<Object>) 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++;}}}}//这里是浅拷贝,与父值引用的是同一个引用,如果需要特殊处理,要覆盖此方法。protected T childValue(T parentValue) {return parentValue;}
上面解释为什么 父线程的InheritableThreadLocal变量可以传递给子线程。但是父线程或者子线程再次通过set命令赋值,不会互相影响。因为关系的建立仅在初始化子线程时建立。
线程安全问题
ThreadLocal不能解决共享变量的线程安全问题,如果没有子线程,则安全问题可以保证,但是如果有子线程,多个子线程引用的是同一个对象,如果都对此对象的属性进行修改,则会导致线程安全问题。
ThreadLocal使用的正确姿势
public static final ThreadLocal<DateFormat> DATE_FORMAT_THREAD_LOCAL = new InheritableThreadLocal<DateFormat>() {@Overrideprotected DateFormat initialValue() {return new SimpleDateFormat("yyyy-MM-dd");}
};
每次绑定时,都会产生一个新的对象。
ThreadLocal局限性
ThreadLocal它并不能解决线程安全问题,它旨在用于传递数据。但是它能成功传递数据比如有个大前提:放数据和取数据的操作必须是处于相同线程。
InheritableThreadLocal,它能够支持跨线程传递数据,但也仅限于父线程给子线程来传递数据。但是对于没有任何关系的2个线程,它无能为力。
线程池搭配问题
由于线程池中是缓存使用过的线程,当线程被重复调用的时候并没有再重新初始化init()线程,而是直接使用已经创建过的线程,所以值并不会被再次操作。因为实际的项目中线程池的使用频率非常高,每一次从线程池中取出线程不能够直接使用之前缓存的变量,所以要解决这一个问题,网上大部分是推荐使用alibaba的开源项目transmittable-thread-local。
TransmittableThreadLocal
JDK的InheritableThreadLocal类可以完成父线程到子线程的值传递。但对于使用线程池等会池化复用线程的执行组件的情况,线程由线程池创建好,并且线程是池化起来反复使用的;这时父子线程关系的ThreadLocal值传递已经没有意义,应用需要的实际上是把 任务提交给线程池时的ThreadLocal值传递到 任务执行时。
参考官网:https://github.com/alibaba/transmittable-thread-local
源码解析参考:https://www.cnblogs.com/hama1993/p/10409740.html
数据结构
数据流程
说明
通过包装的ExecutorTtlWrapper提交Runnable时,一定不能是TtlRunnable,会抛出异常,只能是非TtlRunnalbe,会自动包装,保证每次运行时都会获取主线程的值。这样才有可能在主线程值变更后可以获取到。
ThreadLocal以及增强相关推荐
- 这玩意比ThreadLocal叼多了,吓得我赶紧分享出来。
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! Dubbo的一次提交开始 故事得从前段时间翻阅 Dubbo ...
- 这玩意比 ThreadLocal 叼多了
Dubbo的一次提交开始 故事得从前段时间翻阅 Dubbo 源码时,看到的一段代码讲起. 这段代码就是这个: org.apache.dubbo.rpc.RpcContext 使用 InternalTh ...
- threadlocal使用场景_这玩意比ThreadLocal叼多了,吓得我赶紧分享出来
这是why哥的第 70 篇原创文章 Dubbo的一次提交开始 故事得从前段时间翻阅 Dubbo 源码时,看到的一段代码讲起. 这段代码就是这个: org.apache.dubbo.rpc.RpcCon ...
- 又踩到Dubbo的坑,但是这次我笑不出来
前 言 直入主题,线上应用发现,偶发性出现如下异常日志 当然由于线上具体异常包含信息量过大,秉承让肥朝的粉丝没有难调试的代码的原则,我特意抽取了一个复现的demo放在了git,让你不在现场,一样享受到 ...
- JAVA跨线程传递数据方式总结
实现跨线程传递数据方式: v1:子线程使用主线程的局部变量 这种当主线程和子线程不在一快儿时就不适用.可以使用JDK原生的InheritableThreadLocal. v2:InheritableT ...
- Spring AOP 增强框架 Nepxion Matrix 详解
点击上方"方志朋",选择"置顶或者星标" 你的关注意义重大! 概述 在<深入聊一聊 Spring AOP 实现机制>一文中,介绍了 Spring A ...
- (转)Spring中ThreadLocal的认识
我们知道Spring通过各种DAO模板类降低了开发者使用各种数据持久技术的难度.这些模板类都是线程安全的,也就是说,多个DAO可以复用同一个模板实例而不会发生冲突.我们使用模板类访问底层数据,根据持久 ...
- Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失(续)
前言 上篇文章<Spring Cloud中Hystrix 线程隔离导致ThreadLocal数据丢失>我们对ThreadLocal数据丢失进行了详细的分析,并通过代码的方式复现了这个问题. ...
- spring线程并发处理(ThreadLocal)
我们知道Spring通过各种DAO模板类降低了开发者使用各种数据持久技术的难度.这些模板类都是线程安全的,也就是说,多个DAO可以复用同一个模板实例而不会发生冲突. 我们使用模板类访问底层数据,根据持 ...
最新文章
- 模型大十倍,性能提升几倍?谷歌研究员进行了一番研究
- ubuntu16.04安装英伟达(NVIDIA)驱动——run文件安装
- java float 转double_将float转换为double而不会丢失精度
- ORACLE TEXT DATASTORE PREFERENCE(七)
- WordPress程序备受喜爱的原因:十八般武艺
- win11设置打开闪退怎么办,解决win11系统闪退的问题
- php 音频上传大小限制,WordPress最大上传文件大小限制修改 | Stay Curious
- OLAP-impala-大数据Week13-DAY6-impala
- 2串口两串口三串口多串口3串口转WiFi透传模块实现多通道与服务器透传
- php ctype xdigit,php ctype_digit() 函数介绍
- android 菜鸟面单打印_android菜鸟 实战项目之简单界面实现
- 如何免费使用内网穿透
- 数论--P8845 [传智杯 #4 初赛] 小卡和质数
- openGauss长沙Meetup | 共建数据库可信开源社区
- 微信分享内容给朋友、朋友圈、QQ、QQ空间等
- Android 相机开发
- Android 集成百度地图之申请TTS授权最新版
- 用于AD620系列仪表放大器的RFI抑制电路
- html5 index属性,深入理解CSS z-index属性
- spatialreg | 空间滞后模型、空间误差模型和空间杜宾模型简单形式的R语言实现...