如何吃透一个Java项目?(附学习实践)(2)

// Note about the holder:

// 1. holder self is a InheritableThreadLocal(a ThreadLocal).

// 2. The type of value in the holder is WeakHashMap<TransmittableThreadLocal, ?>.

// 2.1 but the WeakHashMap is used as a Set:

// the value of WeakHashMap is always null, and never used.

// 2.2 WeakHashMap support null value.

private static final InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal, ?>> holder =

new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal, ?>>() {

@Override

protected WeakHashMap<TransmittableThreadLocal, ?> initialValue() {

return new WeakHashMap<TransmittableThreadLocal, Object>();

}

@Override

protected WeakHashMap<TransmittableThreadLocal, ?> childValue(WeakHashMap<TransmittableThreadLocal, ?> parentValue) {

return new WeakHashMap<TransmittableThreadLocal, Object>(parentValue);

}

};

/**

  • see {@link InheritableThreadLocal#set}

*/

@Override

public final void set(T value) {

if (!disableIgnoreNullValueSemantics && null == value) {

// may set null to remove value

remove();

} else {

super.set(value);

addThisToHolder();

}

}

private void addThisToHolder() {

if (!holder.get().containsKey(this)) {

holder.get().put((TransmittableThreadLocal) this, null); // WeakHashMap supports null value.

}

}

结合set方法实现来看,我们会发现holder变量设计的非常巧妙,业务设置的上下文value部分继续复用ThreadLocal原有数据结构ThreadLocalMap来存储( super.set(value));capture的数据源利用holder进行引用存储(addThisToHolder put this)。这样的好处是既可保持ThreadLocal数据存储原有的封装性,又很好实现扩展。除此之外,holder还有其他设计考究,这里抛出来大家可以思考下:

  1. 为什么holder需要设计成static final类级别变量?

  2. ttl变量的存储为什么需要使用WeakHashMap,而不是hashmap或其他?

然后我们再来看异步task转换 TtlRunnable.get(task2) 核心代码实现,代码整体实现相对比较简单,get方法是一个静态工厂方法,主要作用是将业务传入的普通Runnable task装饰成TtlRunable类,并在TtlRunable构造方法中进行线程capture动作(具体实现我们后面再分析),然后将结果存储到对象属性capturedRef中。

@Nullable

public static TtlRunnable get(@Nullable Runnable runnable, boolean releaseTtlValueReferenceAfterRun, boolean idempotent) {

if (null == runnable) return null;

if (runnable instanceof TtlEnhanced) {

// avoid redundant decoration, and ensure idempotency

if (idempotent) return (TtlRunnable) runnable;

else throw new IllegalStateException(“Already TtlRunnable!”);

}

//将入参runnable进行了装饰

return new TtlRunnable(runnable, releaseTtlValueReferenceAfterRun);

}

//…

public final class TtlRunnable implements Runnable, TtlWrapper, TtlEnhanced, TtlAttachments {

private final AtomicReference capturedRef;

private final Runnable runnable;

private final boolean releaseTtlValueReferenceAfterRun;

private TtlRunnable(@NonNull Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {

this.capturedRef = new AtomicReference(capture());

this.runnable = runnable;

this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;

}

/**

  • wrap method {@link Runnable#run()}.

*/

@Override

public void run() {

final Object captured = capturedRef.get();

if (captured == null || releaseTtlValueReferenceAfterRun && !capturedRef.compareAndSet(captured, null)) {

throw new IllegalStateException(“TTL value reference is released after run!”);

}

final Object backup = replay(captured);

try {

runnable.run();

} finally {

restore(backup);

}

}

//…

然后是run方法,这也是核心关键的CRR操作了。这里通过模板方法将CRR操作编排在业务逻辑执行的前后了,也即业务逻辑执行前会将capturer的值进行replay恢复,执行后进行复原restore操作。同样这里也有几个问题很值我们思考:

  1. capture操作为什么需要放到TtlRunnable构造方法中,而不能在run方法中?

  2. 代码中使用了哪两个设计模式,使用设计模式的好处是什么?

  3. 业务执行完之后为什么还需要restore操作?

接下来,我们再分别对capture、replay、restore方法实现做个一一分析。首先是capture方法,我们可以看到capture操作整体比较简单,主要是将set操作保存到holder变量中的值进行遍历并以Snapshot结构进行存储返回。

/**

  • Capture all {@link TransmittableThreadLocal} and registered {@link ThreadLocal} values in the current thread.

  • @return the captured {@link TransmittableThreadLocal} values

  • @since 2.3.0

*/

@NonNull

public static Object capture() {

return new Snapshot(captureTtlValues(), captureThreadLocalValues());

}

private static HashMap<TransmittableThreadLocal, Object> captureTtlValues() {

HashMap<TransmittableThreadLocal, Object> ttl2Value = new HashMap<TransmittableThreadLocal, Object>();

for (TransmittableThreadLocal threadLocal : holder.get().keySet()) {

ttl2Value.put(threadLocal, threadLocal.copyValue());

}

return ttl2Value;

}

private static HashMap<ThreadLocal, Object> captureThreadLocalValues() {

final HashMap<ThreadLocal, Object> threadLocal2Value = new HashMap<ThreadLocal, Object>();

for (Map.Entry<ThreadLocal, TtlCopier> entry : threadLocalHolder.entrySet()) {

final ThreadLocal threadLocal = entry.getKey();

final TtlCopier copier = entry.getValue();

threadLocal2Value.put(threadLocal, copier.copy(threadLocal.get()));

}

return threadLocal2Value;

}

另一个captureThreadLocalValues,主要是用于将一些已有ThreadLocal中的上下文一起复制,已有ThreadLocal需要通过registerThreadLocal方法来单独注册。相关代码如下:

public static class Transmitter {

//…

private static volatile WeakHashMap<ThreadLocal, TtlCopier> threadLocalHolder = new WeakHashMap<ThreadLocal, TtlCopier>();

private static final Object threadLocalHolderUpdateLock = new Object();

//…

public static boolean registerThreadLocal(@NonNull ThreadLocal threadLocal, @NonNull TtlCopier copier, boolean force) {

if (threadLocal instanceof TransmittableThreadLocal) {

logger.warning(“register a TransmittableThreadLocal instance, this is unnecessary!”);

return true;

}

synchronized (threadLocalHolderUpdateLock) {

if (!force && threadLocalHolder.containsKey(threadLocal)) return false;

WeakHashMap<ThreadLocal, TtlCopier> newHolder = new WeakHashMap<ThreadLocal, TtlCopier>(threadLocalHolder);

newHolder.put((ThreadLocal) threadLocal, (TtlCopier) copier);

threadLocalHolder = newHolder;

return true;

}

}

//…

}

这里代码有个非常关键的处理,由于WeakHashMap非线程安全,为了避免并发问题安全加上了synchronized锁操作。这里有可以思考下除了synchronized关键字还有什么保障线程安全的方法。另外,实现threadLocal注册时为已经在锁块中了,为什么还要做new copy重新替换操作,这样做目的是什么?大家可以想想看。

最后就是replay和restore方法,整体实现逻辑非常清晰,主要是将captured的值在当前线程ThreadLocal中进行重新赋值初始化,以及业务执行后恢复到原来。这里很佩服作者对不同情况的细致考虑,不是直接将当前holder中的上下文直接备份,而是与之前已capture的内容比较,将业务后set的上下文进行剔除,以免在恢复restore时出现前后不一致的情况。

@NonNull

public static Object replay(@NonNull Object captured) {

final Snapshot capturedSnapshot = (Snapshot) captured;

return new Snapshot(replayTtlValues(capturedSnapshot.ttl2Value), replayThreadLocalValues(capturedSnapshot.threadLocal2Value));

}

@NonNull

private static HashMap<TransmittableThreadLocal, Object> replayTtlValues(@NonNull HashMap<TransmittableThreadLocal, Object> captured) {

HashMap<TransmittableThreadLocal, Object> backup = new HashMap<TransmittableThreadLocal, Object>();

for (final Iterator<TransmittableThreadLocal> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {

TransmittableThreadLocal threadLocal = iterator.next();

// backup

backup.put(threadLocal, threadLocal.get());

// clear the TTL values that is not in captured

// avoid the extra TTL values after replay when run task

if (!captured.containsKey(threadLocal)) {

iterator.remove();

threadLocal.superRemove();

}

}

// set TTL values to captured

setTtlValuesTo(captured);

// call beforeExecute callback

doExecuteCallback(true);

return backup;

}

private static void setTtlValuesTo(@NonNull HashMap<TransmittableThreadLocal, Object> ttlValues) {

for (Map.Entry<TransmittableThreadLocal, Object> entry : ttlValues.entrySet()) {

TransmittableThreadLocal threadLocal = entry.getKey();

threadLocal.set(entry.getValue());

}

}

public static void restore(@NonNull Object backup) {

final Snapshot backupSnapshot = (Snapshot) backup;

restoreTtlValues(backupSnapshot.ttl2Value);

restoreThreadLocalValues(backupSnapshot.threadLocal2Value);

}

private static void restoreTtlValues(@NonNull HashMap<TransmittableThreadLocal, Object> backup) {

// call afterExecute callback

doExecuteCallback(false);

for (final Iterator<TransmittableThreadLocal> iterator = holder.get().keySet().iterator(); iterator.hasNext(); ) {

TransmittableThreadLocal threadLocal = iterator.next();

// clear the TTL values that is not in backup

// avoid the extra TTL values after restore

if (!backup.containsKey(threadLocal)) {

iterator.remove();

threadLocal.superRemove();

}

}

// restore TTL values

setTtlValuesTo(backup);

}

核心代码分析完之后,再来简单总结下项目中学习到的知识点:

  1. 对ThreadLocal、InheritableThreadLocal有了更加系统和深入的理解,包括两者继承关系、底层数据结构ThreadLocalMap与Thread关联关系等。

  2. 面向gc编程(gc相关)、WeakHashMap(Java对象引用类型强、软、弱等)、线程安全、并发等等

  3. 设计模式相关,装饰模式、工厂、模板方法、代理等

  4. TTL虽然代码量不算多,但短小精悍,也处处体现了作者超高的设计和编程能力,每行代码都值得学习和反复琢磨。

我相信通过类似这样的一个项目学习流程下来,把每个环节都能踏踏实实做好,且过程中有贯穿自己思考和理解。相信你一定能把每个项目吃透,并把项目中的每个技术点都牢牢掌握。

最后,我所在团队是淘系技术部淘系架构团队,主要在负责一站式serverless研发平台建设,为业务不断提升研发效率和极致体验。平台已平稳支撑淘系互动、淘宝人生、金币庄园、特价版、闲鱼、拍卖、品牌轻店等多个业务的6.18、双11、双12、春晚等多个大促活动。


阿里巴巴集团淘系技术部招聘啦~

欢迎加入淘系架构团队,团队成员大牛云集,有阿里移动中间件的创始人员、Dubbo核心成员、更有一群热爱技术,期望用技术推动业务的小伙伴。

淘系架构团队,推进淘系(淘宝、天猫等)架构升级,致力于为淘系、整个集团提供基础核心能力、产品与解决方案:

  • 业务高可用的解决方案与核心能力(精细化流量管控Marconi平台:为业务提供自适应流控、隔离与熔断的柔性高可用解决方案,站点高可用:故障自愈、多机房与异地容灾与快速切流恢复

  • 一站式serverless研发平台GAIA,为业务提供高效研发效率和极致体验。

  • 下一代网络协议QUIC实现与落地

  • 移动中间件(API网关MTop、接入层AServer、消息/推送、配置中心等等)

期待一起参与加入淘系基础平台的建设~

简历投递至少千??? :zhiheng.gao@alibaba-inc.com

✿  拓展阅读

作者|少千

**编辑|**橙子君
**出品|**阿里巴巴新零售淘系技术

复习的面试资料

这些面试全部出自大厂面试真题和面试合集当中,小编已经为大家整理完毕(PDF版)

  • 第一部分:Java基础-中级-高级

image

  • 第二部分:开源框架(SSM:Spring+SpringMVC+MyBatis)

image

  • 第三部分:性能调优(JVM+MySQL+Tomcat)

image

  • 第四部分:分布式(限流:ZK+Nginx;缓存:Redis+MongoDB+Memcached;通讯:MQ+kafka)

image

  • 第五部分:微服务(SpringBoot+SpringCloud+Dubbo)

image

  • 第六部分:其他:并发编程+设计模式+数据结构与算法+网络

image

进阶学习笔记pdf

  • Java架构进阶之架构筑基篇(Java基础+并发编程+JVM+MySQL+Tomcat+网络+数据结构与算法

image

  • Java架构进阶之开源框架篇(设计模式+Spring+SpringMVC+MyBatis

image

image

image

  • Java架构进阶之分布式架构篇 (限流(ZK/Nginx)+缓存(Redis/MongoDB/Memcached)+通讯(MQ/kafka)

image

image

image

  • Java架构进阶之微服务架构篇(RPC+SpringBoot+SpringCloud+Dubbo+K8s)

image

image

92)]

进阶学习笔记pdf

  • Java架构进阶之架构筑基篇(Java基础+并发编程+JVM+MySQL+Tomcat+网络+数据结构与算法

[外链图片转存中…(img-cRVrqrNu-1714678253392)]

  • Java架构进阶之开源框架篇(设计模式+Spring+SpringMVC+MyBatis

[外链图片转存中…(img-7MVwddXb-1714678253392)]

[外链图片转存中…(img-17tAyLQh-1714678253393)]

[外链图片转存中…(img-MB3BeGLR-1714678253393)]

  • Java架构进阶之分布式架构篇 (限流(ZK/Nginx)+缓存(Redis/MongoDB/Memcached)+通讯(MQ/kafka)

[外链图片转存中…(img-E9sJqNVO-1714678253394)]

[外链图片转存中…(img-7UmSizKY-1714678253394)]

[外链图片转存中…(img-fNUl3c4L-1714678253394)]

  • Java架构进阶之微服务架构篇(RPC+SpringBoot+SpringCloud+Dubbo+K8s)

[外链图片转存中…(img-3mRo1ZUY-1714678253395)]

[外链图片转存中…(img-UJeT7wIn-1714678253395)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 23
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值