当前位置: 首页 > news >正文

正版视频素材网站网站主题的分类

正版视频素材网站,网站主题的分类,网页设计的目的,深圳网站导航Java - ThreadLocal数据存储和传递方式的演变之路 前言一. InheritableThreadLocal - 父子线程数据传递1.1 父子线程知识预热和 InheritableThreadLocal 实现原理1.2 InheritableThreadLocal 的诟病 二. TransmittableThreadLocal (TTL) 横空出世2.1 跨线程变量传递测试案例2.2… Java - ThreadLocal数据存储和传递方式的演变之路 前言一. InheritableThreadLocal - 父子线程数据传递1.1 父子线程知识预热和 InheritableThreadLocal 实现原理1.2 InheritableThreadLocal 的诟病 二. TransmittableThreadLocal (TTL) 横空出世2.1 跨线程变量传递测试案例2.2 TTL的基本原理2.2.1 静态属性 holder 和 threadLocalHolder2.2.2 静态内部类 Transmitter 2.3 总结 前言 我在 Java - ThreadLocal原理 这篇文章里面主要介绍了ThreadLocal的使用、基本原理。不过有一点需要在这里做个特殊提醒 ThreadLocal 仅仅用于单线程内的上下文数据传递。多线程情况下数据隔离。 但是现实往往并没有那么简单我们开发过程中往往都有多线程的场景。有时候更需要一些变量在多线程中传递。那ThreadLocal显然并不满足这样的场景那么我们来看看它的一个 “演变之路”。 一. InheritableThreadLocal - 父子线程数据传递 我们先用ThreadLocal来演示一个小Demo我们先准备一个实体类User Data ToString public class User {private static final ThreadLocalInteger THREAD_LOCAL new ThreadLocal();private String name;private Integer age;public static void setAge(Integer age) {THREAD_LOCAL.set(age);System.out.println(当前线程: Thread.currentThread().getName() , 赋值: age);}public static Integer getAge() {Integer res Optional.ofNullable(THREAD_LOCAL.get()).orElse(0);System.out.println(当前线程: Thread.currentThread().getName() , 获取到的值: res);return res;} }然后编写测试用例 public static void main(String[] args) throws InterruptedException {User.setAge(100);// 异步获取Thread t new Thread(() - User.getAge());t.start();t.join();// 同步获取User.getAge();System.out.println(**************Finish****************); }执行结果如下 我们得出以下结论 我们在主线程中往ThreadLocal塞的值只有主线程才能看到子线程看不到。父子线程之间使用ThreadLocal无法进行数据传递。线程隔离。 1.1 父子线程知识预热和 InheritableThreadLocal 实现原理 在上面的案例中我们在主线程中new了一个子线程。结合结果图文来看也就是Thread-0是main的一个子线程。为啥呢我们来看下一下线程的初始化动作 public Thread(Runnable target) {init(null, target, Thread- nextThreadNum(), 0); } ↓↓↓↓↓↓↓↓ 最终都会走到下面的代码 ↓↓↓↓↓↓↓↓// 这里有个参数inheritThreadLocals代表是否继承线程的本地变量们默认是true private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {// 当前线程作为父线程Thread parent currentThread();// 如果需要继承变量并且父线程中的变量非空即拷贝一份变量浅拷贝if (inheritThreadLocals parent.inheritableThreadLocals ! null)this.inheritableThreadLocals ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);// 赋值一个线程ID自增tid nextThreadID(); }创建线程的时候当前线程总是会作为父线程。若父线程绑定了变量并且允许继承那么就会把变量拷贝到子线程里面。浅拷贝若需要深拷贝则需要重写childValue()函数 我们再来看下InheritableThreadLocal 的源码 public class InheritableThreadLocalT extends ThreadLocalT {// 重写childValue函数返回父线程绑定的变量引用。也是浅拷贝protected T childValue(T parentValue) {return parentValue;}ThreadLocalMap getMap(Thread t) {return t.inheritableThreadLocals;}// 如果父线程当中绑定的变量不为null就可以在子线程中创建一份拷贝void createMap(Thread t, T firstValue) {t.inheritableThreadLocals new ThreadLocalMap(this, firstValue);} }针对这一点我们如果将上述案例Demo中的这行代码修改为 private static final ThreadLocalInteger THREAD_LOCAL new ThreadLocal(); ↓↓↓↓↓↓↓↓ private static final ThreadLocalInteger THREAD_LOCAL new InheritableThreadLocal();再次运行程序可得 我们Debug下走下流程 主线程赋值调用到了ThreadLocal的set函数。由于我们使用的是InheritableThreadLocal重写了getMap返回的是主线程的inheritableThreadLocals引用默认为null。 发现为null就会调用createMap函数同样InheritableThreadLocal重写了它 因此这一步执行完毕之后主线程的inheritableThreadLocals属性是有值的。然后创建子线程的时候调用new Thread构造函数 发现主线程已经绑定了相关变量因此会将该变量传递给子线程同样赋值于inheritableThreadLocals变量上。后续以此类推我们只关心inheritableThreadLocals变量上是否有值。从而实现父子线程的变量传递。 1.2 InheritableThreadLocal 的诟病 InheritableThreadLocal 虽然能解决父子之间的变量传递问题。但是大家从源码的角度来看传递变量的关键步骤它实现于线程的创建过程。那么如果说我有个单例线程池复用同一个线程会有什么问题 private static final ExecutorService THREAD_POOL Executors.newSingleThreadExecutor();public static void main(String[] args) throws InterruptedException {User.setAge(100);THREAD_POOL.execute(() - User.getAge());TimeUnit.SECONDS.sleep(2);User.setAge(200);THREAD_POOL.execute(() - User.getAge());TimeUnit.SECONDS.sleep(2);// 同步获取User.getAge();System.out.println(**************Finish****************); }执行结果如下 如果我们 结果虽意料之内但是又是给人惊喜 虽然父子线程间的变量传递成功了但是当值发生变更的时候。子线程拿到的值依旧是老的。结合上面的源码来看也就是说通过InheritableThreadLocal 拿到的变量永远是第一次创建子线程时父线程中存储的变量值。后续不再更改。因为我们本案例中使用的是单例线程池线程对象只会new一次。 总结就是 InheritableThreadLocal中拷贝的数据始终来自于第一个提交任务的父线程这样非常容易造成线程本地变量混乱。 由于是浅拷贝一旦传递链路上变量值被改变那么整个链路线程上的所有变量都会随之更改。还有个很重要的点就是InheritableThreadLocal只支持父子线程间的数据传递而不支持跨线程之间的数据传递 二. TransmittableThreadLocal (TTL) 横空出世 TransmittableThreadLocal 简称TTL是阿里写的一个专门用于解决InheritableThreadLocal诟病的一个功能类。可以在使用线程池等会池化复用线程的执行组件情况下提供ThreadLocal值的传递功能解决异步执行时上下文传递的问题。 针对上述Demo单例线程池我们做出如下更改添加Pom依赖 dependencygroupIdcom.alibaba/groupIdartifactIdtransmittable-thread-local/artifactIdversion2.11.4/version /dependencyInheritableThreadLocal替换成TTL private static final ThreadLocalInteger THREAD_LOCAL new InheritableThreadLocal(); ↓↓↓↓↓↓↓↓ private static final ThreadLocalInteger THREAD_LOCAL new TransmittableThreadLocal();以及线程池做一下装饰 private static final ExecutorService THREAD_POOL Executors.newSingleThreadExecutor(); ↓↓↓↓↓↓↓↓ private static final ExecutorService THREAD_POOL TtlExecutors.getTtlExecutorService(Executors.newSingleThreadExecutor());执行结果如下 从这里可以发现TTL解决了什么问题 JDK自带的的InheritableThreadLocal虽然能在父子之间进行变量传递但是这个变量只有在创建子线程的时候才会被创建并传递。并不会感应其更新。 我们再看另外一个案例 public static void main(String[] args) throws InterruptedException {User.setAge(100);User.getAge();THREAD_POOL.execute(() - {User.getAge();User.setAge(200);User.getAge();});TimeUnit.SECONDS.sleep(2);THREAD_POOL.execute(() - {User.getAge();User.setAge(300);User.getAge();});TimeUnit.SECONDS.sleep(2);User.setAge(666);THREAD_POOL.execute(() - {User.getAge();User.setAge(400);User.getAge();});TimeUnit.SECONDS.sleep(2);// 同步获取User.getAge();System.out.println(**************Finish****************); }运行结果如下 从这个结果可以得出以下结论 子线程拿到的ThreadLocal变量总是最新的。依据的是主线程的变量主线程在修改变量值为666之后子线程pool-1-thread-3获取到的值是最新的666。子线程之间的变量赋值操作互相不影响子线程哪怕更改了ThreadLocal变量在执行结束之后会恢复备份即原值。 所以上图中pool-1-thread-2线程第一次拿到的值依旧是100。 其实上面这个案例还并不是很明显因为我们的主线程始终只有一个。我们来看下一个彻彻底底的跨线程案例。来个高并发看看。 2.1 跨线程变量传递测试案例 需求背景 我们准备一个变量这里我们准备用AtomicInteger。方便修改值。准备一个线程池A用于做业务处理。准备一个线程池B用于模拟HTTP请求层面的高并发。这样每个HTTP请求都有属于自己的主线程也就是有个主变量。我们要验证的就是HTTP层面的并发对变量的影响。业务处理要做的事情将AtomicInteger类型的index变量累加至10。 我们先来看一下InheritableThreadLocal版本的 public class TestTTL {// 定义一个线程池执行ttl这里必须要用TTL线程池封装private static ExecutorService TTL_EXECUTOR TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(5));// 定义另外一个线程池循环执行模拟业务场景下多Http请求调用的情况private static ExecutorService HTTP_EXECUTOR Executors.newFixedThreadPool(5);private static AtomicInteger INDEX new AtomicInteger(0);// TTL的ThreadLocalprivate static ThreadLocal THREAD_LOCAL new TransmittableThreadLocal(); //这里采用TTL的实现public static void main(String[] args) {while (true) {/*** 模拟HTTP请求的高并发*/HTTP_EXECUTOR.execute(() - {// 累加到10我们就停止if (INDEX.get() 10) {THREAD_LOCAL.set(INDEX.getAndAdd(1));TTL_EXECUTOR.execute(() - {System.out.println(String.format(子线程名称-%s, 变量值%s, Thread.currentThread().getName(), THREAD_LOCAL.get()));});}});}} }运行结果如下 很直观的是我们打印了10条记录这一块是由AtomicInteger控制的但是我们存入到InheritableThreadLocal中的变量却没有累加到10说明啥无法跨线程传递变量。别急我们再看下TTL版本的 public class TestTTL {// 定义一个线程池执行ttl这里必须要用TTL线程池封装private static ExecutorService TTL_EXECUTOR TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(5));// 定义另外一个线程池循环执行模拟业务场景下多Http请求调用的情况private static ExecutorService HTTP_EXECUTOR Executors.newFixedThreadPool(5);private static AtomicInteger INDEX new AtomicInteger(0);// TTL的ThreadLocalprivate static ThreadLocal THREAD_LOCAL new TransmittableThreadLocal(); //这里采用TTL的实现public static void main(String[] args) {while (true) {/*** 模拟HTTP请求的高并发*/HTTP_EXECUTOR.execute(() - {// 累加到10我们就停止if (INDEX.get() 10) {THREAD_LOCAL.set(INDEX.getAndAdd(1));TTL_EXECUTOR.execute(() - {System.out.println(String.format(子线程名称-%s, 变量值%s, Thread.currentThread().getName(), THREAD_LOCAL.get()));});}});}} }结果如下 这个结果和上面的结果一对比就可以发现使用TTL进行封装在跨线程的变量传递下它是生效的。 2.2 TTL的基本原理 首先TTL是在InheritableThreadLocal的基础上开发的也就是说TTL继承于InheritableThreadLocal类。我们来看下官网给出的一个执行时序图 在讲这个流程之前我们先来看下TTL中几个比较重要的成员类和属性。 2.2.1 静态属性 holder 和 threadLocalHolder TTL中有个静态属性holder它的特点和作用有以下几点 存储所有使用了TTL的引用类。用于复制值的时候可以通过这个holder去遍历到所有的TTL。key就是当前TTL。value则固定是null。虽然使用了WeakHashMap作为存储但是它的使用方式被用来当做Set集合。 我们来看下这个静态字段 private static InheritableThreadLocalWeakHashMapTransmittableThreadLocalObject, ? holder new InheritableThreadLocalWeakHashMapTransmittableThreadLocalObject, ?() {Overrideprotected WeakHashMapTransmittableThreadLocalObject, ? initialValue() {return new WeakHashMapTransmittableThreadLocalObject, Object();}Overrideprotected WeakHashMapTransmittableThreadLocalObject, ? childValue(WeakHashMapTransmittableThreadLocalObject, ? parentValue) {return new WeakHashMapTransmittableThreadLocalObject, Object(parentValue);}};我们再来看下TTL在get/set的时候做了啥先看set函数 Override public final void set(T value) {// disableIgnoreNullValueSemantics 控制序列化TTL对象的时候是否忽略空值if (!disableIgnoreNullValueSemantics null value) {// may set null to remove valueremove();} else {super.set(value);addThisToHolder();} }我们先说下disableIgnoreNullValueSemantics这个参数在啥场景下需要用到当我们在线程之间传递变量在赋值的时候如果变量值为null那么在新的线程中这个变量将不会存在因此有些时候我们需要将这个属性值设置为true保证变量值在传递过程中不会丢失哪怕其值为null。 根据代码来看也就是默认情况下如果变量传递过程中值为null就会将它剔除。反之如果正常的存储流程是怎样的呢 super.set(value); addThisToHolder();先调用了父类的set函数在InheritableThreadLocal的基础上在调用了addThisToHolder函数。就是将当前TTL引用存储到这个静态变量holder中罢了。 SuppressWarnings(unchecked) private void addThisToHolder() {if (!holder.get().containsKey(this)) {holder.get().put((TransmittableThreadLocalObject) this, null); // WeakHashMap supports null value.} }那总结下就是 TTL有一个静态属性holder用来存储所有的TTL引用。在set赋值变量的时候会触发将当前TTL存储进去。 我们再来看下另外一个字段threadLocalHolder private static volatile WeakHashMapThreadLocalObject, TtlCopierObject threadLocalHolder new WeakHashMapThreadLocalObject, TtlCopierObject();它的作用是对于在项目中使用了ThreadLocal但是却无法替换为TTL那么这个时候就可以使用Transmitter提供的注册方法将项目中的threadLocal注册到threadLocalHolder中。在生成快照的时候也会对这部分变量进行处理。案例代码 // 注册 Transmitter.registerThreadLocalWithShadowCopier(threadLocal); // 注销 Transmitter.unregisterThreadLocal(threadLocal);2.2.2 静态内部类 Transmitter TTL中有一个静态核心内部类Transmitter它主要作用于线程切换的时候其功能如下 ThreadLocal变量的快照保存capture。重放replay。快照恢复restore。 我们先看下快照的创建主线程执行 public static class Transmitter {public static Object capture() {return new Snapshot(captureTtlValues(), captureThreadLocalValues());}// 循环遍历当前holder记录过的所有TTL引用将TTL取出来并保存到Map里面private static WeakHashMapTransmittableThreadLocalObject, Object captureTtlValues() {WeakHashMapTransmittableThreadLocalObject, Object ttl2Value new WeakHashMapTransmittableThreadLocalObject, Object();for (TransmittableThreadLocalObject threadLocal : holder.get().keySet()) {ttl2Value.put(threadLocal, threadLocal.copyValue());}return ttl2Value;}// 循环遍历注册过的ThreadLocal。普通的private static WeakHashMapThreadLocalObject, Object captureThreadLocalValues() {final WeakHashMapThreadLocalObject, Object threadLocal2Value new WeakHashMapThreadLocalObject, Object();for (Map.EntryThreadLocalObject, TtlCopierObject entry : threadLocalHolder.entrySet()) {final ThreadLocalObject threadLocal entry.getKey();final TtlCopierObject copier entry.getValue();threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get()));}return threadLocal2Value;} }再来看下快照的重放子线程执行 public static Object replay(NonNull Object captured) {final Snapshot capturedSnapshot (Snapshot) captured;return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value)); }跟快照创建很像我们这里分析ttl2ValueTTL引用就不再分析threadLocal2Value原生ThreadLocal的注册我们看下replayTtlValues函数 // 入参 captured 是从其他线程TTL中捕获到的变量 private static WeakHashMapTransmittableThreadLocalObject, Object replayTtlValues(NonNull WeakHashMapTransmittableThreadLocalObject, Object captured) {// 创建一个备份MapWeakHashMapTransmittableThreadLocalObject, Object backup new WeakHashMapTransmittableThreadLocalObject, Object();// 遍历当前所有的TTL引用for (final IteratorTransmittableThreadLocalObject iterator holder.get().keySet().iterator(); iterator.hasNext(); ) {TransmittableThreadLocalObject threadLocal iterator.next();// 将当前子线程的数据进行备份backup.put(threadLocal, threadLocal.get());// 如果快照中不存在当前TTL实例则要删除因为有些TTL引用可能是在调用capture生成快照之后才创建的。if (!captured.containsKey(threadLocal)) {iterator.remove();threadLocal.superRemove();}}// 将快照值赋值到当前线程中setTtlValuesTo(captured);// 执行execute之前的一些逻辑doExecuteCallback(true);return backup; }最后来看下快照的恢复操作 public static void restore(NonNull Object backup) {final Snapshot backupSnapshot (Snapshot) backup;restoreTtlValues(backupSnapshot.ttl2Value);restoreThreadLocalValues(backupSnapshot.threadLocal2Value); }同样我们关注ttlValue private static void restoreTtlValues(NonNull WeakHashMapTransmittableThreadLocalObject, Object backup) {doExecuteCallback(false);// 遍历所有的TTLfor (final IteratorTransmittableThreadLocalObject iterator holder.get().keySet().iterator(); iterator.hasNext(); ) {TransmittableThreadLocalObject threadLocal iterator.next();// 如果备份中不存在当前TTL实例则要删除因为有些TTL引用可能是在调用capture生成快照之后才创建的。if (!backup.containsKey(threadLocal)) {iterator.remove();threadLocal.superRemove();}}// 重新将备份的值设置一下setTtlValuesTo(backup); }一般我们会将自定义的线程池用TtlExecutors进行封装这样里面的任务就会被TtlRunnable封装。那么我们看下TtlRunnable类 public final class TtlRunnable implements Runnable, TtlWrapperRunnable, TtlEnhanced, TtlAttachments {private final AtomicReferenceObject capturedRef;private final Runnable runnable;private final boolean releaseTtlValueReferenceAfterRun;// 创建任务时调用的构造函数private TtlRunnable(NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {// 调用capture函数即创建一个快照。this.capturedRef new AtomicReferenceObject(capture());// 将原生的业务代码Jobs进行保存。this.runnable runnable;// this.releaseTtlValueReferenceAfterRun releaseTtlValueReferenceAfterRun;}/*** wrap method {link Runnable#run()}.*/Overridepublic void run() {// 子线程开始执行先取得快找数据。Object captured capturedRef.get();if (captured null || releaseTtlValueReferenceAfterRun !capturedRef.compareAndSet(captured, null)) {throw new IllegalStateException(TTL value reference is released after run!);}// 将快照中的数据设置到当前线程中因为这些数据来自于其他线程当前线程还没有赋值呢同时创建一个备份数据backupObject backup replay(captured);try {// 执行真正的业务逻辑runnable.run();} finally {// 最后恢复备份快照restore(backup);}} }2.3 总结 总结下TTL的原理大概如下 首先我们使用的线程池需要通过TtlExecutors进行封装本质上和单独的任务被TtlRunnable封装是一致的。只不过前者更方便了。TtlRunnable封装过的任务也就是存储了当前TTL快照的一个引用。在创建的时候就会捕捉当前父线程中的TTL以及一些注册过的ThreadLocal就是用于兼容老版本的ThreadLocal让他们有跨线程传递的特性TTL中有一个静态属性holder存储了所有的TTL引用。存储的时机在我们set的时候发生。子线程开始执行的时候先调用Transmitter.replay获取到其他线程里面存储的变量也就是上一步产生的快照capturedRef这里体现到了跨线程的变量传递然后将值赋值给当前线程。同时产生一个备份backup并返回。子线程执行完毕执行Transmitter.restore根据backup备份将数据恢复。 FAQ 为什么TTL中需要有一个holder静态变量用来存储所有的TTL引用呢回答 holder首先是一个InheritableThreadLocal里面放的是一个WeakHashMap。用来收集线程中所有的TTL。反之如果我们不用holder是否可以拿到其他线程中存储的变量呢Thread中的ThreadLocal.ThreadLocalMap inheritableThreadLocals 成员变量它的值虽然可以被子线程继承。但是我们在业务代码中无法对这个变量直接访问。因此还是需要通过自己定义的holder进行存储和访问。 为什么子线程执行完毕之后要恢复快照难道不是下一次线程复用的时候对应线程存储的TTL变量吗回答 当线程池满了并且采取的拒绝策略是CallerRunsPolicy。那么此时原本要执行子业务逻辑的操作可能会在主线程中执行。也就是两个操作在同一个线程中执行。倘若没有restore这个操作倘若中途对TTL的内容进行更改就会导致最终上下文数据被污染。 下面是我从笑傲君这截取的图这个是正常的变量拷贝图子线程可以对TTL中的内容做任意修改拷贝了一份 以下则是同线程执行情况下这种情况下若发生TTL值变更就会发生上下文污染。 TTL存在线程安全问题吗回答 存在因为默认是引用类型拷贝如果子线程修改了数据主线程可以感知的到。
http://www.w-s-a.com/news/797307/

相关文章:

  • 北京市保障性住建设投资中心网站首页专业做网站联系电话
  • 镇江网站建设方式优化单页面网站教程
  • 做手机网站公司北京网页设计公司兴田德润实惠
  • 域名申请好了 要怎么做网站百度推广开户渠道
  • 电商网站建设 数商云焦作黄河交通学院
  • 做一个网站成本多少太原网站维护
  • 网站建设制作设计优化怎么制作网页步骤
  • 花都区pc端网站建设画册设计多少钱一页
  • 国外买域名的网站廊坊网站制作网页
  • 抚顺市城市建设档案馆网站制作网页时经常用的一种动态位图格式是
  • 公司网站站群是什么运营网站
  • 昆明网站建设大全安徽教育机构网站建设
  • 广州网站排名怎么优化中华衣柜网
  • 怎样围绕网站专题发展来做ppt城乡住建局官网
  • 安卓手机app制作关键词优化公司
  • 江苏固茗建设有限公司网站深圳网站建设深圳网
  • 高性能网站建设指南北京城乡建设官方网站
  • 企业网站找谁做做淘宝相关网站
  • 商业网站网站建设wordpress关闭前端公共库
  • 打开山东城市建设职业学院网站下载了wordpress后
  • 四川网站建设设计城乡建设网站证件查询系统
  • 企业邮箱哪里买栾城seo整站排名
  • 长沙网站建设zh68网页制作技术实训报告
  • 电商网站的功能手机广告设计与制作软件
  • 做网站前端需要编程基础吗杭州市住房和城乡建设局
  • 网站开发一般学多久网站建设投标方案
  • 北京网站建设报价表制作短视频的软件有哪些
  • 长沙企业网站开发西安建设公司网站
  • 做图的兼职网站网站开发用了哪些知识要点
  • php网站怎么做静态化微慕wordpress插件