alibaba / fastjson2

🚄 FASTJSON2 is a Java JSON library with excellent performance.
Apache License 2.0
3.7k stars 479 forks source link

[BUG] JSONFactory和ObjectReaderProvider静态代码循环依赖导致死锁 #2994

Open yhzdys opened 1 day ago

yhzdys commented 1 day ago

问题描述

项目中的一些组件需要一个独立的JSONUtil工具类,通过自定义ObjectReaderProvider来实现一些自定义的逻辑。 在项目启动时,如果有多个并发请求同时调用JSON的静态方法和自定义的JSONUtil,有概率出现线程死锁。

排查下来的原因是:

类ObjectReaderProvider和JSONFactory的静态代码块出现了循环依赖。 类加载器在初始化class时会给上一个class级别的锁以保证类的static代码只执行一次。

ObjectReaderProvider中的static代码块需要依赖JSONFactory完成初始化, 但是JSONFactory中的defaultObjectReaderProvider静态属性需要依赖ObjectReaderProvider完成初始化。

环境信息

重现步骤

运行下面代码,可能出现死锁,导致系统无响应。

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.reader.ObjectReaderProvider;
import com.alibaba.fastjson2.writer.ObjectWriterProvider;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class Test {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(2);
        Thread thread1 = new Thread(() -> {
            System.out.println("1 start");
            LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(10L)); // 停顿时间可能需要根据环境调整
            JSONUtil.test();
            latch.countDown();
        }, "thread-1");
        Thread thread2 = new Thread(() -> {
            System.out.println("2 start");
            JSON.parseObject("{}");
            latch.countDown();
        }, "thread-2");

        thread1.start();
        thread2.start();
        System.out.println("waiting");
        latch.await();
        System.out.println("finished");
    }

    public static class JSONUtil {

        private static final ObjectReaderProvider READER;
        private static final ObjectWriterProvider WRITER;

        static {
            // 解决方法,随便调一个JSONFactory的方法来保证加载顺序与默认顺序一致
//            JSONFactory.getDefaultObjectReaderProvider();

            READER = new ObjectReaderProvider();
            WRITER = new ObjectWriterProvider();
        }

        public static String test() {
            return "ok";
        }
    }
}

期待的正确结果

目前时通过JSONUtil中注释掉的方法来保证类的加载顺序一致。 但是这种静态代码循环依赖的形式,并非一种好的实现,一旦出现问题很难排查,或许有改进的必要?

相关日志输出

1 start
2 start
waiting

附加信息