今天本来在研究 线程传递参数的方式,ThreadLocal MDC,来一篇吧。。
是的,百度一下 都知道,但是 怎么证明? 首先
- MDC.put -》
- mdcAdapter.put(key, val); -》
-
- MDCAdapter 实现类 LogbackMDCAdapter
-
- 复制代码
- //这里 它传入的是 1
- private Integer getAndSetLastOperation(int op) {
- Integer lastOp = (Integer)this.lastOperation.get();
- this.lastOperation.set(op);
- return lastOp;
- }
- 复制代码
外面
- public void put(String key, String val) throws IllegalArgumentException {
- if (key == null) {
- throw new IllegalArgumentException("key cannot be null");
- } else {
- Map<String, String> oldMap = (Map)this.copyOnThreadLocal.get();
- //设置为 1,并且 返回原来的旧值 代表着上次是什么操作
- Integer lastOp = this.getAndSetLastOperation(1);
- //上次是写操作 并且oldMap 不是null 就在oldMap 基础 上put
- if (!this.wasLastOpReadOrNull(lastOp) && oldMap != null) {
- oldMap.put(key, val);
- } else {
- Map<String, String> newMap = this.duplicateAndInsertNewMap(oldMap);
- newMap.put(key, val);
- }
-
- }
- }
-
- //需要返回 false 才代表 之前有oldMap 操作,这里的判断 不是null 不是2,看了一遍代码 这不就是1么
- private boolean wasLastOpReadOrNull(Integer lastOp) {
- return lastOp == null || lastOp == 2;
- }
- 复制代码
这就需要去看看 什么时候 会lastOp 为2
如果是 2就是上次操作是 读呢,这个时候 有put 操作 就 新创建一个空的newMap 赋值,如果原来是 1 代表这个线程 上次就是 put ,在原来的oldMap 基础上 进行put。
返回 a 和 b
一debug ,调用的是另一个获取方法,那什么时候 调用这个赋值为2的方法呢?
接口中没有这个方法,留个疑问 继续往下
上面说到 Thread 中有 ThreadLocal.ThreadLocalMap
上面的东西 看完,感觉应该结尾了吧。。。NO, 2022年这个put 方法有变化了。。。。
往下继续看 注释 fix LOGBACK-1684 using code from LOGBACK-620
既然你这么说了 我就分别看看 这两个
代码改动在 github.com/qos-ch/logb…
大概意思 在对我们的应用程序进行性能分析时,LogbackMDCAdapter显示为性能热点。这是因为应用程序确实经常替换MDC中的多个条目,例如 删除/写入而不使用任何中间日志语句。另外,在生产环境中,在创建LoggingEvent之前经过过滤的实际日志消息相对较少。我重做了实现,以提高性能。其主要思想是尽可能推迟克隆内部Map。该补丁在该测试中提高了应用程序的总体性能约10%
里面 为了替换原来的 flag 判断,现在多了
- final ThreadLocal<Map<String, String>> readWriteThreadLocalMap = new ThreadLocal<Map<String, String>>();
- final ThreadLocal<Map<String, String>> readOnlyThreadLocalMap = new ThreadLocal<Map<String, String>>();
- 复制代码
下面评论区 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 大佬 最后说 经过测试 你写的确实不错 哈哈
版本升级后
- //这个方法 原来是 赋值为2的,现在修改了
- public Map<String, String> getPropertyMap() {
- Map<String, String> readOnlyMap = readOnlyThreadLocalMap.get();
- if (readOnlyMap == null) {
- Map<String, String> current = readWriteThreadLocalMap.get();
- if (current != null) {
- final Map<String, String> tempMap = new HashMap<String, String>(current);
- readOnlyMap = Collections.unmodifiableMap(tempMap);
- readOnlyThreadLocalMap.set(readOnlyMap);
- }
- }
- return readOnlyMap;
- }
- 复制代码
像不像 Eureka 的多级缓存机制,都一个道理
里面一个点 是 使用了 Collections.unmodifiableMap,所有对外返回的list map等引用参数,都要重新赋值一遍 / 只读不能操作。防止 外部改变这个元素 影响我们。
不可变类 也是一个道理,返回需要 重新生成一个,不会直接返回 原来的元素,不可变类 防止的是 一个对象中有20个属性, 防止20个属性出现中间态,比如 5个属性 属于v1, 15个属性 属于v2 这种中间态情况。