log4j中MDC使用陷阱

1. 背景

同学求助,生产环境对于方法JSONObject.toJSONString()时不时的报以下错误,一看这个大家都会知道HashMap中modCount发生了变化,与初始化生成迭代器HashIterator的expectedModCount不同,那modCount发生变化的场景:clear(),putVal(),removeNode(),computeIfAbsent(),compute(),merge().一定是在遍历过程发生的HashMap增删变化,这个毋庸置疑。接下来我看实际场景

Exception in thread "pool-1-thread-2" java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
    at java.util.HashMap$EntryIterator.next(HashMap.java:1463)
    at java.util.HashMap$EntryIterator.next(HashMap.java:1461)
    at com.alibaba.fastjson.serializer.MapSerializer.write(MapSerializer.java:79)
    at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:361)
    at com.alibaba.fastjson.JSON.toJSONString(JSON.java:594)
    at TestThread.lambda$main$1(TestThread.java:33)
  1. 对问题代码进行了抽取, 如下

        
            log4j
            log4j
            1.2.17
        

        
            com.alibaba
            fastjson
            1.2.3
        
    
public class TestThread {
    public static JSONObject getJSONObject() {
        JSONObject json = (JSONObject) MDC.get("json");
        if (json == null) {
            json = new JSONObject();
            MDC.put("json", json);
        }
        jsonObject.clear();
        return json;
    }

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        boolean flag = true;
        if (flag) {
            final JSONObject jsonObject = getJSONObject();
            executorService.execute(() -> {
                System.out.println("父线程json与子线程json是否相等:" + (jsonObject == getJSONObject()));
            });
        }
        executorService.execute(() -> {
            while (true) {
                getJSONObject().toJSONString();
            }
        });
        executorService.execute(() -> {
            while (true) {
                JSONObject jsonObject1 = getJSONObject();
                while (true) {
                    jsonObject1.put("K_" + System.currentTimeMillis(), null);
                }
            }
        });
    }
}
  1. 运行结果
当flag=true
父线程json与子线程json是否相等:true
Exception in thread "pool-1-thread-2" java.util.ConcurrentModificationException
    at java.util.HashMap$HashIterator.nextNode(HashMap.java:1429)
    at java.util.HashMap$EntryIterator.next(HashMap.java:1463)
    at java.util.HashMap$EntryIterator.next(HashMap.java:1461)
    at com.alibaba.fastjson.serializer.MapSerializer.write(MapSerializer.java:79)
    at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:361)
    at com.alibaba.fastjson.JSON.toJSONString(JSON.java:594)
    at TestThread.lambda$main$1(TestThread.java:33)
    at TestThread$$Lambda$2/457233904.run(Unknown Source)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)

当flag=false
无报错结果
  1. 分析定位到org.apache.log4j.MDC问题:错误使用InheritableThreadLocal
  2. InheritableThreadLocal子线程会copy父线程中的所有ThreadLocal的变量,对于引用类型JSONObject则导致发现并发操作问题
  3. 解决方法
private static final ThreadLocal  JSON_OBJECT_THREAD_LOCAL= new ThreadLocal();

    public static JSONObject getJSONObject(){
        JSONObject jsonObject = JSON_OBJECT_THREAD_LOCAL.get();
        if (jsonObject == null){
            jsonObject = new JSONObject();
            JSON_OBJECT_THREAD_LOCAL.set(jsonObject);
        }
        jsonObject.clear();
        return jsonObject;
    }

你可能感兴趣的:(log4j中MDC使用陷阱)