网站换友链平台网络营销方式的演变
- 作者: 五速梦信息网
- 时间: 2026年03月21日 07:53
当前位置: 首页 > news >正文
网站换友链平台,网络营销方式的演变,网站关键词优化是什么,广州网站开发广州亦客网络解答文章目录 一、MDC是什么1.1 MDC常用API1.2 MDC数据结构 二、MDC的SPI机制2.1 LogbackMDCAdapter绑定过程 三、MDC源码解析3.1 MDC源码3.2 LogbackMDCAdapter源码3.2.1 Write - Write 场景分析3.2.2 Write - Copy map - Write 场景分析 四、MDC的局限性4.1 父子线程… 文章目录 一、MDC是什么1.1 MDC常用API1.2 MDC数据结构 二、MDC的SPI机制2.1 LogbackMDCAdapter绑定过程 三、MDC源码解析3.1 MDC源码3.2 LogbackMDCAdapter源码3.2.1 Write - Write 场景分析3.2.2 Write - Copy map - Write 场景分析 四、MDC的局限性4.1 父子线程数据无法传递4.2 线程池使用MDC存在数据传递重复4.3 如何破局 五、总结 一、MDC是什么
MDC全称为Mapped Diagnostic Context是著名日志框架SLF4J提供的一种方便多线程条件下记录日志的工具类在分布式链路跟踪系统实现上MDC绝对算是是一把核心利器。MDC的直观理解可看做一个与当前线程绑定的Map这个Map用来存储线程私有信息。怎么做到与线程绑定呢用到的就是JDK的ThreadLocal楼主之前也有专门问章分析过ThreadLocal见【了不起的ThreadLocal】一、源码分析
1.1 MDC常用API
使用MDC非常简单定义的常用API也就如下6个静态方法
void put(String key, String val)String get(String key)void remove(String key)void clear()MapString, String getCopyOfContextMap()void setContextMap(MapString, String contextMap)
1.2 MDC数据结构
MDC数据结构如下图所示简单概括下:
MDC持有一个MDCAdapter这个MDCAdapter是一个接口实现交给具体日志组件比如在Logback的实现类就是LogbackMDCAdapterMDC可视作一个门面类对外交互的所有API都会经过MDC再委托给具体的MDCAdapter来执行。 二、MDC的SPI机制
在Java生态里面日志组件非常丰富比较普及的有Log4j、Logback、Log4j2等前面讲到MDC持有的MDCAdapter仅仅是一个接口运行时具体用到的是哪个MDCAdapter的实现完全交给应用系统来决定。比如应用系统采用的是Logback则MDC绑定的将是LogbackMDCAdapter。这个绑定过程是通过SLF4J定义的一套SPI扩展机制来实现的下文会结合MDC部分源码来更好地理解下这个SPI机制。
2.1 LogbackMDCAdapter绑定过程
如上图所示MDC完整类路径为org.slf4j.MDC注意看上图slf4j-api这个jar包是没有org.slf4j.impl这个包的。这里就得提到第一个规范SLF4J的SPI机制强制要求实现SLF4J日志标准的组件必须将spi实现类的绑定逻辑统一放在org.slf4j.impl这个包下面并且类名和部分方法名是固定不能瞎写。
不信对照Logback这个jar包结构看下。
以Logback为例来看看具体的绑定过程:
1.当客户端调用MDC的任一静态方法时将触发MDC这个类的初始化进而触发static静态代码块执行(这个机制是JVM的规范static代码块只会被执行一次)2.在static静态代码块中主要有两条途径加载具体MDC绑定类: 第一种是通过调用org.slf4j.impl这个包的StaticMDCBinder.getSingleton().getMDCA()很遗憾logback里面没有这个方法故抛出异常第二种是调用org.slf4j.impl包 StaticMDCBinder.SINGLETON.getMDCA()方法刚好Logback就是这么定义的找到了返回的就是LogbackMDCAdapter故最终MDC成功绑定的就是LogbackMDCAdapter
到这来SPI就说完了没错这个SPI机制就是这么简单粗暴得不敢相信
注意下面的代码在slf4j里面 注意: org.slf4j.impl.StaticMDCBinder这个对象引用实际SLF4J 这个jar包里面是没有这个类的但MDC.java文件又引入了为何编译器不报错原因在于用到了animal-sniffer-maven-plugin这个Maven插件这个插件的细节楼主还没仔细看感兴趣的同学可以自行研究下 import java.io.Closeable;
import java.util.Map;import org.slf4j.helpers.NOPMDCAdapter;
import org.slf4j.helpers.BasicMDCAdapter;
import org.slf4j.helpers.Util;// 注意这个包的引用实际SLF4J里面没有这个类)
import org.slf4j.impl.StaticMDCBinder;import org.slf4j.spi.MDCAdapter;public class MDC {static final String NULL_MDCA_URL http://www.slf4j.org/codes.html#null_MDCA;static final String NO_STATIC_MDC_BINDER_URL http://www.slf4j.org/codes.html#no_static_mdc_binder;static MDCAdapter mdcAdapter;/*** As of SLF4J version 1.7.14, StaticMDCBinder classes shipping in various bindings* come with a getSingleton() method. Previously only a public field called SINGLETON * was available.* * return MDCAdapter* throws NoClassDefFoundError in case no binding is available* since 1.7.14/private static MDCAdapter bwCompatibleGetMDCAdapterFromBinder() throws NoClassDefFoundError {try {// logback没有这个方法故会进入到catch代码块里面return StaticMDCBinder.getSingleton().getMDCA();} catch (NoSuchMethodError nsme) {// logback里面能找到// binding is probably a version of SLF4J older than 1.7.14return StaticMDCBinder.SINGLETON.getMDCA();}}// MDC类初始化时会执行static {try {// 绑定具体的MDCAdapter实现mdcAdapter bwCompatibleGetMDCAdapterFromBinder();} catch (NoClassDefFoundError ncde) {mdcAdapter new NOPMDCAdapter();String msg ncde.getMessage();if (msg ! null msg.contains(StaticMDCBinder)) {Util.report(Failed to load class \org.slf4j.impl.StaticMDCBinder.);Util.report(Defaulting to no-operation MDCAdapter implementation.);Util.report(See NO_STATIC_MDC_BINDER_URL for further details.);} else {throw ncde;}} catch (Exception e) {// we should never get hereUtil.report(MDC binding unsuccessful., e);}}
}注意这里的代码在Logback里面并非slf4j
public class StaticMDCBinder {/** The unique instance of this class./public static final StaticMDCBinder SINGLETON new StaticMDCBinder();private StaticMDCBinder() {}/** Currently this method always returns an instance of * {link StaticMDCBinder}./public MDCAdapter getMDCA() {return new LogbackMDCAdapter();}public String getMDCAdapterClassStr() {return LogbackMDCAdapter.class.getName();}
}三、MDC源码解析
3.1 MDC源码
知道MDC干活全靠委派给MDCAdapter那MDC的源码其实就不用细看了以下瞟一眼大概知道有哪几个API并且意识到get、put、remove方法都要求key不能null就可以略过了。
public class MDC {static final String NULL_MDCA_URL http://www.slf4j.org/codes.html#null_MDCA;static final String NO_STATIC_MDC_BINDER_URL http://www.slf4j.org/codes.html#no_static_mdc_binder;static MDCAdapter mdcAdapter;public static void put(String key, String val) throws IllegalArgumentException {if (key null) {throw new IllegalArgumentException(key parameter cannot be null);}if (mdcAdapter null) {throw new IllegalStateException(MDCAdapter cannot be null. See also NULL_MDCA_URL);}mdcAdapter.put(key, val);}public static String get(String key) throws IllegalArgumentException {if (key null) {throw new IllegalArgumentException(key parameter cannot be null);}if (mdcAdapter null) {throw new IllegalStateException(MDCAdapter cannot be null. See also NULL_MDCA_URL);}return mdcAdapter.get(key);}public static void remove(String key) throws IllegalArgumentException {if (key null) {throw new IllegalArgumentException(key parameter cannot be null);}if (mdcAdapter null) {throw new IllegalStateException(MDCAdapter cannot be null. See also NULL_MDCA_URL);}mdcAdapter.remove(key);}public static void clear() {if (mdcAdapter null) {throw new IllegalStateException(MDCAdapter cannot be null. See also NULL_MDCA_URL);}mdcAdapter.clear();}/** Return a copy of the current threads context map, with keys and values of* type String. Returned value may be null.* * return A copy of the current threads context map. May be null.* since 1.5.1/public static MapString, String getCopyOfContextMap() {if (mdcAdapter null) {throw new IllegalStateException(MDCAdapter cannot be null. See also NULL_MDCA_URL);}return mdcAdapter.getCopyOfContextMap();}/** Set the current threads context map by first clearing any existing map and* then copying the map passed as parameter. The context map passed as* parameter must only contain keys and values of type String.* * param contextMap* must contain only keys and values of type String* since 1.5.1/public static void setContextMap(MapString, String contextMap) {if (mdcAdapter null) {throw new IllegalStateException(MDCAdapter cannot be null. See also NULL_MDCA_URL);}mdcAdapter.setContextMap(contextMap);}}3.2 LogbackMDCAdapter源码
LogbackMDCAdapter是真正的主角MDC的数据读/写都交给了它。太阳底下无新鲜事LogbackMDCAdapter最核心的数据存储结构就是2个ThreadLocal围绕这2个ThreadLocal完成MDC的全部接口功能。故看懂LogbackMDCAdapter的关键在于看懂这2个ThreadLocal的设计用意
public class LogbackMDCAdapter implements MDCAdapter {// MDC真正用来存数据的容器final ThreadLocalMapString, String copyOnThreadLocal new ThreadLocalMapString, String();private static final int WRITE_OPERATION 1;private static final int MAP_COPY_OPERATION 2;// 记录上一次操作类型写为1(put、remove、cleare都是写),copy map为2// keeps track of the last operation performedfinal ThreadLocalInteger lastOperation new ThreadLocalInteger();private Integer getAndSetLastOperation(int op) {// 上一次操作类型Integer lastOp lastOperation.get();// 记录当前操作lastOperation.set(op);// 返回上一次操作类型return lastOp;}private boolean wasLastOpReadOrNull(Integer lastOp) {return lastOp null || lastOp.intValue() MAP_COPY_OPERATION;}}了解源码最好的办法就是把代码run起来。楼主写了几个测试类下面就按照测试类的执行思路展开源码分析。
3.2.1 Write - Write 场景分析
测试Case1
public class MdcTest {Testpublic void testMdc() {// 打印出当前绑定的MDCAdapterSystem.out.println(MDC.getMDCAdapter().getClass().getName());// 第一次操作putMDC.put(a, 123);dumpMdc();// 第二次操作putMDC.put(b, 456);dumpMdc();// 第三次操作putMDC.remove(b);dumpMdc();}public static void dumpMdc() {System.out.println(MDC.getCopyOfContextMap());}}输出
ch.qos.logback.classic.util.LogbackMDCAdapter
{a123}
{a123, b456}
{a123}为便于直观理解配几幅图来动态分析代码执行流程靠想象力自行在脑海模拟JVM运行上述代码) LogbackMDCAdapter 类初始状态 简单描述下LogbackMDCAdapter这个类初始化之后copyOnThreadLocal、lastOperation两个final变量(ps: final变量实际存放在方法区图上画在栈内存纯属偷懒)分别指向ThreadLocalMapString, String copyOnThreadLocal 和 ThreadLocal lastOperation 这两个ThreadLocal此时当前线程(main线程)的ThreadLocalMap(即threadLocals属性)还没存放值。 a.第一次操作执行put(a, 123)
public class LogbackMDCAdapter implements MDCAdapter {private Integer getAndSetLastOperation(int op) {// 上一次操作类型Integer lastOp lastOperation.get();// 记录当前操作lastOperation.set(op);// 返回上一次操作类型return lastOp;}private boolean wasLastOpReadOrNull(Integer lastOp) {return lastOp null || lastOp.intValue() MAP_COPY_OPERATION;}// 第一次put操作时oldMap为nullprivate MapString, String duplicateAndInsertNewMap(MapString, String oldMap) {// new了一个新map对象MapString, String newMap Collections.synchronizedMap(new HashMapString, String());if (oldMap ! null) {// we dont want the parent thread modifying oldMap while we are// iterating over itsynchronized (oldMap) {newMap.putAll(oldMap);}}// 对ThreadLocal的某个Entry赋值假设就是Entry[0], 其key就是copyOnThreadLocal其value为空mapcopyOnThreadLocal.set(newMap);return newMap;}public void put(String key, String val) throws IllegalArgumentException {if (key null) {throw new IllegalArgumentException(key cannot be null);}// 第一次put操作时因为main线程的ThreadLocalMap还是空的故从copyOnThreadLocal定位不到任何Entry故返回的oldMap为nullMapString, String oldMap copyOnThreadLocal.get();// 记录当前操作为写(new一个Entry[3]并将value设置为1)返回的lastOp为nullInteger lastOp getAndSetLastOperation(WRITE_OPERATION);if (wasLastOpReadOrNull(lastOp) || oldMap null) {// 进到这个分支并且oldMap为null; MapString, String newMap duplicateAndInsertNewMap(oldMap);// 调用duplicateAndInsertNewMap之后的结果是创建出了Entry[0], 并且其value为一个空map// 将a123塞到newMap, 由于newMap和Entry[0].value指向的是同一个map对象故最终效果就是a123写到了main线程的ThreadLocal里面了理解这个语句非常重要)newMap.put(key, val);} else {oldMap.put(key, val);}}}这里留一个问题为什么lastOp为copy map时要new一个map出来 b.第二次操作执行put(b, 456)
public class LogbackMDCAdapter implements MDCAdapter {public void put(String key, String val) throws IllegalArgumentException {if (key null) {throw new IllegalArgumentException(key cannot be null);}// 第二次put操作时从copyOnThreadLocal定位到Entry[0]的value为{a123}MapString, String oldMap copyOnThreadLocal.get();// 从lastOperation定位到Entry[3]的value为1故返回的lastOp为1并记录当前操作为写(将Entry[3]的value从1设置为1Integer lastOp getAndSetLastOperation(WRITE_OPERATION);if (wasLastOpReadOrNull(lastOp) || oldMap null) {MapString, String newMap duplicateAndInsertNewMap(oldMap);newMap.put(key, val);} else {// lastOp为1且oldMap又不为null故进到这个分支// 直接把当前写操作内容放入oldMap最终Entry[0].value为{a123, b456}oldMap.put(key, val);}}}c.第三次操作remove(b)
public class LogbackMDCAdapter implements MDCAdapter {public void remove(String key) {if (key null) {return;}// 从copyOnThreadLocal定位到Entry[0]的value为{a123, b456}MapString, String oldMap copyOnThreadLocal.get();if (oldMap null)return;// 从lastOperation定位到Entry[3]的value为1故返回的lastOp1Integer lastOp getAndSetLastOperation(WRITE_OPERATION);if (wasLastOpReadOrNull(lastOp)) {MapString, String newMap duplicateAndInsertNewMap(oldMap);newMap.remove(key);} else {// lastOp1进到这个分支直接从oldMap做删除操作删除b之后oldMap为{a123}oldMap.remove(key);}}}3.2.2 Write - Copy map - Write 场景分析
在这一小节回答前面提出的「为什么lastOp为copy map的时候要new一个map出来」还是结合一个测试Case来分析。
测试Case2
public class MdcTest {Test
public void testLogbackMDCAdapter() {LogbackMDCAdapter mdcAdapter new LogbackMDCAdapter();// 第一次操作putmdcAdapter.put(a, 123);// 注意: 方法名虽然看着像在做copy map实际没有记录成copy map操作System.out.println(mdcAdapter.getCopyOfContextMap());// 第二次操作 copy map这个方法里面里面才真正记录了 copy map操作)MapString, String snapshot mdcAdapter.getPropertyMap();System.out.println(snapshot: snapshot);// 第三次操作putmdcAdapter.put(b, 456);System.out.println(mdcAdapter.getCopyOfContextMap());System.out.println(snapshot: snapshot);
}
}输出
{a123}
snapshot: {a123}
{a123, b456}
snapshot: {a123}a.第一次操作执行put(a, 123) 第一次put操作后同前文分析完全一样照搬前面的图内存中对象如下 b.第二次操作getPropertyMap() 注意: LogbackMDCAdapter的copy map操作实际发生在getPropertyMap()方法而不是getCopyOfContextMap()方法。
public MapString, String getPropertyMap() {// 通过lastOperation定位到Entry[3], 将Entry[3].value从1更新为2lastOperation.set(MAP_COPY_OPERATION);// 返回Entry[0].value被变量snapshot接收return copyOnThreadLocal.get();
}c.第三次操作执行put(b, 456)
public class LogbackMDCAdapter implements MDCAdapter {public void put(String key, String val) throws IllegalArgumentException {if (key null) {throw new IllegalArgumentException(key cannot be null);}// 当前put操作时从copyOnThreadLocal定位到Entry[0]的value为{a123}MapString, String oldMap copyOnThreadLocal.get();// 从lastOperation定位到Entry[3]的value为2故返回的lastOp为2并记录当前操作为写(将Entry[3]的value从2设置为1Integer lastOp getAndSetLastOperation(WRITE_OPERATION);if (wasLastOpReadOrNull(lastOp) || oldMap null) {// lastOp2, 故进到这个分支MapString, String newMap duplicateAndInsertNewMap(oldMap);// 调用duplicateAndInsertNewMap(oldMap)得到的newMap为{a123};// 把当前kv写入newMap, 故最终newMap为{a123, b456}newMap.put(key, val);} else {oldMap.put(key, val);}}// 此处传入的oldMap为{a123}private MapString, String duplicateAndInsertNewMap(MapString, String oldMap) {// new了一个新map对象MapString, String newMap Collections.synchronizedMap(new HashMapString, String());if (oldMap ! null) {// we dont want the parent thread modifying oldMap while we are// iterating over itsynchronized (oldMap) {// 将oldMap的内容搬到newMapnewMap.putAll(oldMap);}}// 对ThreadLocal的Entry[0]赋值Entry[0]的value断开对原来oldMap的引用指向newMap最终Entry[0].value为newMap{a123}copyOnThreadLocal.set(newMap);// 返回newMapreturn newMap;}
}到这里应该就能理解为什么需要记录操作lastOperation以及进行oldMap、newMap的复制操作了。根本作用就是为了保证在「write - copy map - write」这种场景下每次copy map得到的都是一个当前快照并且这个快照是不受后面的写操作影响。对照Case2来说第一次put(a, 123)操作后 第二次操作进行copy map得到的快照snapshot为{a123}第三次操作put(b, 456)之后此时mdc里面的内容已经是{a123, b456}但是打印snapshot仍然还是{a123}没有任何变更。因此lastOperation配合oldMap、newMap的复制操作做到的就是快照读的效果。假如没有这种设计在同一个线程里面第一次打印snapshot和第二次打印snapshot会输出不同的值就有点薛定谔了MDC设计最难理解的点就在这里看懂这个快照读机制才算真正理解了MDC。 四、MDC的局限性
4.1 父子线程数据无法传递
前面已经分析清楚了MDC的源码归根到底MDC的数据读写都是基于ThreadLocal。如果你熟悉ThreadLocal肯定知道它还有个子类InheritableThreadLocal用来做父子线程的数据传递。由于LogbackMDCAdapter采用的是ThreadLocal而非InheritableThreadLocal因此存在第一个缺陷就是: 没法做到父子线程的数据传递。写个Case证明下
Case3: 证明MDC父子线程数据无法传递
public class MdcTest {Testpublic void testMdc3() throws InterruptedException {// 打印出当前绑定的MDCAdapterSystem.out.println(MDC.getMDCAdapter().getClass().getName());MDC.put(foo, 123);CountDownLatch latch new CountDownLatch(1);new Thread(() - {System.out.println(Thread.currentThread() : MDC.get(foo));latch.countDown();}, child).start();latch.await();System.out.println(Thread.currentThread() : MDC.get(foo));}}输出:可以看到在child线程里面从MDC取到的值为null也就是说父线程main设置到MDC的值子线程child根本就取不到
ch.qos.logback.classic.util.LogbackMDCAdapter
Thread[child,5,main]: null
Thread[main,5,main]: 123解决父子线程数据传递手段很简单将ThreadLocal换成InheritableThreadLocal就可以InheritableThreadLocal怎么做到的源码还得看Thread类的init方法思路就是每当new一个子线程时就把父线程的inheritableThreadLocals这个ThreadLocaMap复制下赋值给当前子线程的threadLocals。
public
class 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;// 新建一个线程public Thread() {init(null, null, Thread- nextThreadNum(), 0);}public Thread(String name) {init(null, null, name, 0);}private void init(ThreadGroup g, Runnable target, String name,long stackSize, AccessControlContext acc,boolean inheritThreadLocals) {Thread parent currentThread();// 略去无关代码…// inheritThreadLocals 传入的默认值就是trueif (inheritThreadLocals parent.inheritableThreadLocals ! null)// 如果父线程的inheritableThreadLocals不为空则通过ThreadLocal.createInheritedMap将inheritableThreadLocals复制一份赋值给当前线程(子线程)的threadLocals属性this.inheritableThreadLocals ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);// 略去无关代码…}}4.2 线程池使用MDC存在数据传递重复
线程池使用MDC存在数据传递重复这个问题就比较隐晦了。对照下4.1小节讲到的InheritableThreadLocal的原理无非就是在线程新创建的时候对父线程的ThreadLocalMap进行复制操作然后赋值给子线程也就是说当前仅当线程创建的那个时机才开始做复制操作。如果线程是通过线程池创建出来线程循环利用就只会有一次创建机会那么就只会在第一次new的时候复制父线程ThreadLocalMap计时后面父线程后面又更新了999次ThreadLocalMap子线程都不会再去复制这样的后果就是子线程从MDC取到的值一直都是第一次复制的值严格来说这个锅并非是MDC的而是ThreadLocal的缺陷下面还是写个Case证明下
Case4: 证明InheritableThreadLocal在线程池场景下存在数据传递重复
public class MdcTest {Testpublic void testInheritableThreadLocal() throws InterruptedException {final Executor executor Executors.newFixedThreadPool(1);final ThreadLocalInteger threadLocal new InheritableThreadLocal();AtomicInteger times new AtomicInteger(0);for (int i 0; i 3; i) {times.getAndIncrement();// 这里是在父线程上设置的值threadLocal.set(i);String timesStr 第 times.get() 次循环;System.out.println(timesStr Thread.currentThread() : threadLocal.get());try {executor.execute(() - System.out.println(timesStr Thread.currentThread() : threadLocal.get()));} finally {threadLocal.remove();}}Thread.sleep(100);}}输出 第1次循环父线程main和子线程pool-1-thread-1从InheritableThreadLocal取到的值都是0这是对的证明了InheritableThreadLocal确实能从父线程复制到值但是从第2次循环开始父线程的值以及从0变成1、2而子线程的值始终为0这就有问题了证明子线程后续完全不跟随父线程做值的变更
第1次循环Thread[main,5,main]: 0
第1次循环Thread[pool-1-thread-1,5,main]: 0
第2次循环Thread[main,5,main]: 1
第3次循环Thread[main,5,main]: 2
第2次循环Thread[pool-1-thread-1,5,main]: 0
第3次循环Thread[pool-1-thread-1,5,main]: 04.3 如何破局
针对上面2个缺陷MDC本身并未给出解决方案。幸运的是阿里巴巴开源的transmittable-thread-local解决了以上个问题其解决思路1、针对父子线程数据无法传递问题TransmittableThreadLocal继承并加强InheritableThreadLocal类2、针对线程池InheritableThreadLocal数据数据传递存在重复问题TransmittableThreadLocal提供了TtlRunnable和TtlCallable来修饰提交到线程池的任务保证每次任务执行前强制从父线程copy下ThreadLocalMap的最新的值。
值得一提的是除了TransmittableThreadLocal还有一大神器TtlMDCAdapter整合TransmittableThreadLocal的能力并直接实现MDCAdapter接口用起MDC完全感知不到底层原来是TtlMDCAdapter非常顺滑再写个Demo展示下其能力
TtlMDCAdapter用法
public class MdcTest {Testpublic void testTTLMDCAdapter() throws InterruptedException {// MDC绑定TtlMDCAdapterTtlMDCAdapter.getInstance();// 修饰线程池final Executor executor TtlExecutors.getTtlExecutor(Executors.newFixedThreadPool(1));AtomicInteger times new AtomicInteger(0);for (int i 0; i 3; i) {times.getAndIncrement();String key String.valueOf(i);// 这里是在父线上设置的值MDC.put(key, String.valueOf(i));String timesStr 第 times.get() 次循环;System.out.println(timesStr Thread.currentThread() : MDC.get(key));try {executor.execute(() - System.out.println(timesStr Thread.currentThread() : MDC.get(key)));} finally {MDC.remove(key);}}Thread.sleep(100);}}输出 父线程main和子线程pool-1-thread-1三次循环操作父子线程从MDC读取的值完全一一致
第1次循环Thread[main,5,main]: 0
第2次循环Thread[main,5,main]: 1
第1次循环Thread[pool-1-thread-1,5,main]: 0
第3次循环Thread[main,5,main]: 2
第2次循环Thread[pool-1-thread-1,5,main]: 1
第3次循环Thread[pool-1-thread-1,5,main]: 2五、总结
MDC最大的用途在于分布式链路跟踪上真正会用的人就会知道MDC用起来有多么爽MDC的源码分析到此终于告一段落TransmittableThreadLocal和TtlMDCAdapter的源码本文就不再做分析了好奇的同学可自行研究。最后概括本文主要内容
MDC的SPI机制: 以Logback为例讲述了MDC的SPI机制实现原理。有了这个认识读者可再去理解SLF4J对具体日志组件的绑定机制就能做到触类旁通MDC的源码分析: 以源码注释 测试Case 示意图细致地分析了MDC源码设计思路MDC的2大局限性: 结合测试Case分析、证明MDC存在的问题提出解决方案进而引出TransmittableThreadLocal、TtlMDCAdapter两大神器。
全文终~
- 上一篇: 网站换空间要重新备案吗怎么搭建自己的网站
- 下一篇: 网站换域名 蜘蛛不来嘉兴五县两区网站建设
相关文章
-
网站换空间要重新备案吗怎么搭建自己的网站
网站换空间要重新备案吗怎么搭建自己的网站
- 技术栈
- 2026年03月21日
-
网站换空间多少钱wordpress首页五格
网站换空间多少钱wordpress首页五格
- 技术栈
- 2026年03月21日
-
网站换空间 怎么下载汕头网站制作多少钱
网站换空间 怎么下载汕头网站制作多少钱
- 技术栈
- 2026年03月21日
-
网站换域名 蜘蛛不来嘉兴五县两区网站建设
网站换域名 蜘蛛不来嘉兴五县两区网站建设
- 技术栈
- 2026年03月21日
-
网站换域名 蜘蛛不来网站侧边栏设计
网站换域名 蜘蛛不来网站侧边栏设计
- 技术栈
- 2026年03月21日
-
网站换域名后需要多长时间才能收录恢复正常wordpress主题技术网
网站换域名后需要多长时间才能收录恢复正常wordpress主题技术网
- 技术栈
- 2026年03月21日
