• LogbackMDC 2022年有变动?


    今天本来在研究 线程传递参数的方式,ThreadLocal MDC,来一篇吧。。

    MDC 是每个线程 都有一个么?

    是的,百度一下 都知道,但是 怎么证明? 首先

    1. MDC.put -》
    2. mdcAdapter.put(key, val); -》
    3. MDCAdapter 实现类 LogbackMDCAdapter
    4. 复制代码

    上面第50行 可以看到 他获取之前最后一次的 标识,可以没有么?它起到了什么作用?

    1. //这里 它传入的是 1
    2. private Integer getAndSetLastOperation(int op) {
    3. Integer lastOp = (Integer)this.lastOperation.get();
    4. this.lastOperation.set(op);
    5. return lastOp;
    6. }
    7. 复制代码

    外面

    1. public void put(String key, String val) throws IllegalArgumentException {
    2. if (key == null) {
    3. throw new IllegalArgumentException("key cannot be null");
    4. } else {
    5. Map<String, String> oldMap = (Map)this.copyOnThreadLocal.get();
    6. //设置为 1,并且 返回原来的旧值 代表着上次是什么操作
    7. Integer lastOp = this.getAndSetLastOperation(1);
    8. //上次是写操作 并且oldMap 不是null 就在oldMap 基础 上put
    9. if (!this.wasLastOpReadOrNull(lastOp) && oldMap != null) {
    10. oldMap.put(key, val);
    11. } else {
    12. Map<String, String> newMap = this.duplicateAndInsertNewMap(oldMap);
    13. newMap.put(key, val);
    14. }
    15. }
    16. }
    17. //需要返回 false 才代表 之前有oldMap 操作,这里的判断 不是null 不是2,看了一遍代码 这不就是1
    18. private boolean wasLastOpReadOrNull(Integer lastOp) {
    19. return lastOp == null || lastOp == 2;
    20. }
    21. 复制代码

    这就需要去看看 什么时候 会lastOp 为2

    如果是 2就是上次操作是 呢,这个时候 有put 操作 就 新创建一个空的newMap 赋值,如果原来是 1 代表这个线程 上次就是 put ,在原来的oldMap 基础上 进行put。

    上面这个看完 会不会有一个疑问,那我多次 put (a,b 属性)然后 get下,在put a属性,难道我get b就没有了?

    返回 a 和 b

    一debug ,调用的是另一个获取方法,那什么时候 调用这个赋值为2的方法呢?

    接口中没有这个方法,留个疑问 继续往下

    Thread 和 ThreadLocal的关系 是一对一么?

    上面说到 Thread 中有 ThreadLocal.ThreadLocalMap

    变化

    上面的东西 看完,感觉应该结尾了吧。。。NO, 2022年这个put 方法有变化了。。。。

    往下继续看 注释 fix LOGBACK-1684 using code from LOGBACK-620

    既然你这么说了 我就分别看看 这两个

    jira.qos.ch/browse/LOGB…

    代码改动在 github.com/qos-ch/logb…

    为什么改动呢 保持原版不香么

    大概意思 在对我们的应用程序进行性能分析时,LogbackMDCAdapter显示为性能热点。这是因为应用程序确实经常替换MDC中的多个条目,例如 删除/写入而不使用任何中间日志语句。另外,在生产环境中,在创建LoggingEvent之前经过过滤的实际日志消息相对较少。我重做了实现,以提高性能。其主要思想是尽可能推迟克隆内部Map。该补丁在该测试中提高了应用程序的总体性能约10%

    里面 为了替换原来的 flag 判断,现在多了

    1. final ThreadLocal<Map<String, String>> readWriteThreadLocalMap = new ThreadLocal<Map<String, String>>();
    2. final ThreadLocal<Map<String, String>> readOnlyThreadLocalMap = new ThreadLocal<Map<String, String>>();
    3. 复制代码

    讨论

    下面评论区 Ceki 大佬提出,你现在 用了2个对象,是不是可以通过

    Get操作从来不需要复制或连续的“put/remove”操作,只有Get后面跟着“put/remove”操作需要复制map

    这样就可以 so on Note that 8 operations were performed by only 3 maps were created

    但是 Michael 大佬说

    但作为一名软件开发人员,我对你的策略感到有点不舒服:“读后写后复制”听起来与“写后复制”和“惰性复制”不太一样。使用您的方法,您将迫使开发人员关注放置和获取操作的顺序,特别是如果应用程序在写入MDC之前执行存在性检查。

    Ceki 大佬 最后说 经过测试 你写的确实不错 哈哈

    彩蛋

    版本升级后

    1. //这个方法 原来是 赋值为2的,现在修改了
    2. public Map<String, String> getPropertyMap() {
    3. Map<String, String> readOnlyMap = readOnlyThreadLocalMap.get();
    4. if (readOnlyMap == null) {
    5. Map<String, String> current = readWriteThreadLocalMap.get();
    6. if (current != null) {
    7. final Map<String, String> tempMap = new HashMap<String, String>(current);
    8. readOnlyMap = Collections.unmodifiableMap(tempMap);
    9. readOnlyThreadLocalMap.set(readOnlyMap);
    10. }
    11. }
    12. return readOnlyMap;
    13. }
    14. 复制代码

    像不像 Eureka 的多级缓存机制,都一个道理

    里面一个点 是 使用了 Collections.unmodifiableMap,所有对外返回的list map等引用参数,都要重新赋值一遍 / 只读不能操作。防止 外部改变这个元素 影响我们。

    不可变类 也是一个道理,返回需要 重新生成一个,不会直接返回 原来的元素,不可变类 防止的是 一个对象中有20个属性, 防止20个属性出现中间态,比如 5个属性 属于v1, 15个属性 属于v2 这种中间态情况。

  • 相关阅读:
    通达信下单接口有哪些?如何通过程序语言来实现
    9 万字 208 道 Java 经典面试题总结 (附答案), 看到就是赚到
    【LeetCode】15、三数之和
    JavaScript—获取当前时间 并转化为yyyy-MM-dd hh:mm:ss格式
    c语言:深度学习递归
    自动化测试框架(pytest)&附学习视频
    MySQL 用户权限和远程访问设置
    Laravel框架 - IOC容器详解
    ThreadLocal和InheritableThreadLocal实现原理
    TypeError: object list can‘t be used in ‘await‘ expression
  • 原文地址:https://blog.csdn.net/BASK2311/article/details/127595551