线上踩坑-Java集合线程安全问题

2020-05-06  本文已影响0人  花醉霜寒

\color{green}{踩坑回顾}
此前有个汇聚功能的响应时间特别长,走查代码发现以前版本完全用单线程来实现的,我将这个功能简单的抽象了一下,代码实现如下所示:

public class ListThreadSafeTest {
    public static void main(String[] args) throws InterruptedException {
        List<String> list =  new ArrayList<>();
        for(int j = 0; j < 800; j++) {
            //do something
            list.add("aa");
        }
        System.out.println(list.size());
    }
}

采用单线程的方式,业务逻辑为cpu密集型的,当数据量比较大的时候,采用单线程for循环的方式处理,性能可想而知。后来我用CountDownLatch对代码进行了改造,如下所示

public class ListThreadSafeTest {

    public static void main(String[] args) throws InterruptedException {
        List<String> list =  new ArrayList<>();
        //测试代码,这里就不追求细节了
        Executor executor = Executors.newFixedThreadPool(8);
        CountDownLatch latch = new CountDownLatch(8);
        for(int i = 0; i < 8 ; i++) {
            executor.execute(() -> {
                List<String> list1 = new ArrayList<>();
                for(int j = 0; j < 100; j++) {
                    list.add("aa");
                }
                latch.countDown();
            });
        }
        latch.await();
        System.out.println(list.size());
    }
}

测试性能提升十分明显,但是上线之后,该功能出现了大量数据不准确的情况,即list.size()的值不是800,而是随机出现一个小于800的结果,后来排查问题的时候想到,ArrayList不是线程安全的集合,在多线程并发写的过程中会出现线程安全问题,为了解决线程安全问题引入了一个线程安全的集合类ConcurrentHashMap来保存中间结果,问题解决。后来我用HashMap代替ConcurrentHashMap试了一下,同样会出现问题,因为HashMap也是非线程安全的。

public class ListThreadSafeTest {

    public static void main(String[] args) throws InterruptedException {
        List<String> list =  new ArrayList<>();
        //Map<String, List<String>> map = new HashMap<>();
        Map<String, List<String>> map = new ConcurrentHashMap<>();
        //测试代码,这里就不追求细节了
        Executor executor = Executors.newFixedThreadPool(8);
        CountDownLatch latch = new CountDownLatch(8);
        for(int i = 0; i < 8 ; i++) {
            executor.execute(() -> {
                List<String> list1 = new ArrayList<>();
                for(int j = 0; j < 100; j++) {
                    list1.add("aa");
                }
                map.put(UUID.randomUUID().toString(), list1);
                latch.countDown();
            });
        }
        latch.await();
        map.forEach((key, value) -> {
            list.addAll(value);
        });
        System.out.println(list.size());
    }
}

\color{green}{踩坑感想}
平常把高并发和线程安全挂在嘴边,真正到运用的时候却出现这种比较低级的错误,看来学知识还是不能仅限于表面,学以致用才是王道。

上一篇 下一篇

猜你喜欢

热点阅读