生产中遇到的Java多线程问题

本文分析了一起在生产环境中遇到的Java多线程问题,该问题由一个看似正常的单例类引发,由于Map对象的初始化与线程并发问题,导致偶发的NullPointerException。通过问题重现、原因分析、验证以及解决方案的探讨,揭示了volatile关键字在多线程场景下的局限性和正确使用单例模式的重要性。

1. 问题场景:前几天一位同事分享了一段代码,这段代码在线上偶尔会报空指针异常,虽然是一个简单的NullPointerException,却是一个很不容易发现的多线程问题导致的,我们一起先来看下代码(这是我复原的测试代码,并非和生产一致,但整体逻辑是一样的):

public class TestSingleton {

    private static volatile TestSingleton testSingleton = null;

    static Map<String, String> idCard;

    private TestSingleton() {}

    public static TestSingleton init() {

        if (testSingleton == null) {

            synchronized (TestSingleton.class) {

                if (testSingleton == null) {

                    testSingleton = new TestSingleton();

                    idCard = new HashMap<>();

                    idCard.put("aa", "bb");
                }
            }
        }

        return testSingleton;

    }

    public String getIdCard() {

        return idCard.get("aa");

    }


    public static void main(String[] args) {

        TestSingleton testSingleton = TestSingleton.init().getIdCard();    

    }
}

整体来看这位仁兄是实现了一个单例类,看样子没什么问题,只是多了一个Map来实例化并设置一些kv值,从逻辑上来没看出有什么毛病,我一开始看到这个代码确实没看出什么问题来。

如果先不看后续的描述,你能看出问题来吗?

静静地观察一分钟...

2. 问题还原分析:通过观察看出是什么问题了吗?如果你立马定位了原因,那么说明你的多线程还是挺扎实的,接下来我们一块来分析一下问题原因。首先我启动main方法执行,没有发现任何问题,多试几次也没问题,但这并不能说明就真正没问题(要不然生产上的问题就没法解释了),因为我现在这样执行main方法是单线程,只能说明这个代码在单线程场景下是没有任何问题的。现在我将main方法改成多线执行:

public static void main(String[] args) {

        int n = 10;

        CountDownLatch countDownLatch = new CountDownLatch(n);

        ExecutorService executorService = Executors.newFixedThreadPool(n);

        for (int i = 0; i < n; i++) {

            executorService.execute(() -> {

                TestSingleton.init().getIdCard();

                countDownLatch.countDown();

            });

        }

        try {

            //等待所有线程执行完毕

            countDownLatch.await();

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        //统计一下执行情况,看是否都执行完成

        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executorService;

        System.out.println("总任务数:" + threadPoolExecutor.getTaskCount());

  &n
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值