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

资深的网站推广武汉seo关键词优化

资深的网站推广,武汉seo关键词优化,那个装修公司的网站做的好,来一个网站谢谢了文章目录 一、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常用API
    • 1.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()
  • Map<String, String> getCopyOfContextMap()
  • void setContextMap(Map<String, String> contextMap)

1.2 MDC数据结构

MDC数据结构如下图所示,简单概括下:

  • MDC持有一个MDCAdapter,这个MDCAdapter是一个接口,实现交给具体日志组件,比如在Logback的实现类就是LogbackMDCAdapter;
  • MDC可视作一个门面类,对外交互的所有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.implStaticMDCBinder.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 thread's context map, with keys and values of* type String. Returned value may be null.* * @return A copy of the current thread's context map. May be null.* @since 1.5.1*/public static Map<String, String> getCopyOfContextMap() {if (mdcAdapter == null) {throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL);}return mdcAdapter.getCopyOfContextMap();}/*** Set the current thread's 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(Map<String, 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 ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal<Map<String, 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 ThreadLocal<Integer> lastOperation = new ThreadLocal<Integer>();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
{a=123}
{a=123, b=456}
{a=123}

为便于直观理解,配几幅图来动态分析代码执行流程(靠想象力自行在脑海模拟JVM运行上述代码)

  • LogbackMDCAdapter 类初始状态
    简单描述下:LogbackMDCAdapter这个类初始化之后,copyOnThreadLocal、lastOperation两个final变量(ps: final变量实际存放在方法区,图上画在栈内存纯属偷懒),分别指向`ThreadLocal<Map<String, 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 Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap) {// new了一个新map对象Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>());if (oldMap != null) {// we don't 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为nullMap<String, String> oldMap = copyOnThreadLocal.get();// 记录当前操作为写(new一个Entry[3],并将value设置为1),返回的lastOp为nullInteger lastOp = getAndSetLastOperation(WRITE_OPERATION);if (wasLastOpReadOrNull(lastOp) || oldMap == null) {// 进到这个分支,并且oldMap为null; Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);// 调用duplicateAndInsertNewMap之后的结果是创建出了Entry[0], 并且其value为一个空map// 将a=123塞到newMap, 由于newMap和Entry[0].value指向的是同一个map对象,故最终效果就是a=123写到了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为{a=123}Map<String, String> oldMap = copyOnThreadLocal.get();// 从lastOperation定位到Entry[3]的value为1,故返回的lastOp为1;并记录当前操作为写(将Entry[3]的value从1设置为1)Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);if (wasLastOpReadOrNull(lastOp) || oldMap == null) {Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);newMap.put(key, val);} else {// lastOp为1且oldMap又不为null,故进到这个分支// 直接把当前写操作内容放入oldMap,最终Entry[0].value为{a=123, b=456}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为{a=123, b=456}Map<String, String> oldMap = copyOnThreadLocal.get();if (oldMap == null)return;// 从lastOperation定位到Entry[3]的value为1,故返回的lastOp=1Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);if (wasLastOpReadOrNull(lastOp)) {Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);newMap.remove(key);} else {// lastOp=1,进到这个分支;直接从oldMap做删除操作,删除b之后oldMap为{a=123}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操作)Map<String, String> snapshot = mdcAdapter.getPropertyMap();System.out.println("snapshot: " + snapshot);// 第三次操作putmdcAdapter.put("b", "456");System.out.println(mdcAdapter.getCopyOfContextMap());System.out.println("snapshot: " + snapshot);
}
}
  • 输出
{a=123}
snapshot: {a=123}
{a=123, b=456}
snapshot: {a=123}
  • a.第一次操作执行put("a", "123")
    第一次put操作后,同前文分析完全一样,照搬前面的图内存中对象如下:
    在这里插入图片描述

  • b.第二次操作getPropertyMap()
    注意: LogbackMDCAdapter的copy map操作实际发生在getPropertyMap()方法,而不是getCopyOfContextMap()方法。

public Map<String, 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为{a=123}Map<String, String> oldMap = copyOnThreadLocal.get();// 从lastOperation定位到Entry[3]的value为2,故返回的lastOp为2;并记录当前操作为写(将Entry[3]的value从2设置为1)Integer lastOp = getAndSetLastOperation(WRITE_OPERATION);if (wasLastOpReadOrNull(lastOp) || oldMap == null) {// lastOp=2, 故进到这个分支;Map<String, String> newMap = duplicateAndInsertNewMap(oldMap);// 调用duplicateAndInsertNewMap(oldMap)得到的newMap为{a=123};// 把当前kv写入newMap, 故最终newMap为{a=123, b=456}newMap.put(key, val);} else {oldMap.put(key, val);}}// 此处传入的oldMap为{a=123}private Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap) {// new了一个新map对象Map<String, String> newMap = Collections.synchronizedMap(new HashMap<String, String>());if (oldMap != null) {// we don't 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={a=123}copyOnThreadLocal.set(newMap);// 返回newMapreturn newMap;}
}

在这里插入图片描述

到这里应该就能理解为什么需要记录操作lastOperation以及进行oldMap、newMap的复制操作了。根本作用,就是为了保证在「write -> copy map -> write」这种场景下,每次copy map得到的都是一个当前快照,并且这个快照是不受后面的写操作影响。对照Case2来说,第一次put("a", "123")操作后, 第二次操作进行copy map得到的快照snapshot为{a=123},第三次操作put("b", "456")之后,此时mdc里面的内容已经是{a=123, b=456},但是打印snapshot,仍然还是{a=123}没有任何变更。因此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 ThreadLocal<Integer> 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]: 0

4.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两大神器。

全文终~

http://www.mmbaike.com/news/30857.html

相关文章:

  • 黑龙江省住房和建设厅网站网站建站方式有哪些
  • 深圳网站做的好的公司百度付费推广的费用
  • 织梦做的网站打开不是网站推广优化的方法
  • 小贷网站需要多少钱可以做电商网
  • 做画册可以参考哪些网站网站排名提升软件
  • 网站的设计费用关键词采集软件
  • 网络营销推广的方案小学生班级优化大师
  • 长沙网站建设爱站网站长工具
  • 做房产中介网站陕西网站设计
  • wordpress 虎嗅 2017搜索引擎优化的实验结果分析
  • 个人可以做外贸的网站友情链接网站大全
  • vps可以做几个网站小璇seo优化网站
  • wordpress怎么做网站网站流量统计查询
  • 做网站要注册第35类商标吗宁波超值关键词优化
  • 左右翻网站模版永久免费建个人网站
  • 做网站一般长宽多少钱最近有新病毒出现吗
  • 南通网站建设论文谷歌怎么推广自己的网站
  • 动漫网站设计源代码网络品牌推广
  • 卖钢材做哪个宣传网站关键词优化公司前十排名
  • 网站配色 要用什么原则手机推广软文
  • 做pc端网站新闻网站点击软件排名
  • asp网站怎样做app百度站长工具怎么关闭
  • 沈阳建网站 哪家好线上营销推广方式都有哪些
  • 南通网站建设空间石家庄网站建设方案
  • 衡水seo营销搜索引擎外部优化有哪些渠道
  • 武汉网站制作 app开发苏州seo安严博客
  • 如何将自己做的网站放到网上google搜索引擎入口
  • 什么网站可以做国外生意博客网站登录入口
  • 上海企业网站建设费用百度爱采购平台官网
  • 做个网站成本常州seo排名收费