面试官让我写一个 ArrayList 的线程不安全的“bug”,并修复它!

JAVA herman 125浏览
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xmtxtt,发送下载链接帮助你免费下载!
本博客日IP超过1800,PV 2600 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog,之前的微信号好友位已满,备注:返现
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序

我们都知道,ArrayList 天生就不是线程安全的,但是很多人也就是道听途说而已。并没有实际的进行测试,也并不清楚 ArrayList 为什么不是线程安全的!

这不,昨天还有人问我为什么?说面试官让他写一个 bug。答案其实很简单,今天我们就一起来动手写一个“bug”!

写一个 ArrayList 的线程不安全的“bug”

看下面这个测试代码:

public class XttblogUnsafeList {
    public static void main(String[] args) {
        // 进行 10次测试
        for (int i = 0; i < 10; i++) {
            test();
        }
    }
    public static void test() {
        // 用来测试的List
        List<Object> list = new ArrayList<Object>();
        // 线程数量(100)
        int threadCount = 100;
        // 用来让主线程等待threadCount个子线程执行完毕
        CountDownLatch countDownLatch = new CountDownLatch(threadCount);
        // 启动threadCount个子线程
        for (int i = 0; i < threadCount; i++) {
            Thread thread = new Thread(new MyThread(list, countDownLatch));
            thread.start();
        }
        try {
            // 主线程等待所有子线程执行完成,再向下执行
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // List 的size
        System.out.println(list.size());
    }
}
class MyThread implements Runnable {
    private List<Object> list;
    private CountDownLatch countDownLatch;
    public MyThread(List<Object> list, CountDownLatch countDownLatch) {
        this.list = list;
        this.countDownLatch = countDownLatch;
    }
    public void run() {
        // 每个线程向List中添加100个元素
        for (int i = 0; i < 1000; i++) {
            list.add(new Object());
        }
        // 完成一个子线程(主线程等待子线程执行完了再执行)
        countDownLatch.countDown();
    }
}

多次执行后,运行结果有两种。一种就是常见的 java.lang.ArrayIndexOutOfBoundsException 异常。

Exception in thread "Thread-100" java.lang.ArrayIndexOutOfBoundsException: 109
    at java.util.ArrayList.add(ArrayList.java:463)
    at com.xttblog.MyThread.run(XttblogUnsafeList.java:52)
    at java.lang.Thread.run(Thread.java:748)

还有一种就是跑成功了,但是数据不对。甚至时不时会抛出个 IndexOutOfBoundsException 异常,这种情况很少见,多数情况下,都是直接抛出异常。

100000
100000
99847
100000
99416
99442
99998
100000
99271
99926

观察上面的代码,其实很简单,就是多线程,向 list 中 add 元素。但是,最终结果却和预期不一致。为什么会漏掉数据呢?

这是因为 List 对象,在做 add 时,执行 Arrays.copyOf 的时候,返回一个新的数组对象。当有线程 t1、t2、… 同时进入 grow方法,多个线程都会执行 Arrays.copyOf 方法,返回多个不同的 elementData 对象,假如,t1 先返回,t2 后返回,那么 List.elementData == t1.elementData,然后 t2 也返回后,这时 List.elementData == t2.elementData,所以,t2.elementData 就把 t1.elementData 数据给覆盖了。导致 t1.elementData 被丢失。

那么这种问题,怎么解决呢?

一种是使用 Vector,还有一种是使用 Collections.synchronizedList(new ArrayList()) 。但是你也要注意符合操作!

这就是,面试中,让我写一个“bug”,并修复一个“bug”。

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加QQ1群:135430763(2000人群已满),QQ2群:454796847(已满),QQ3群:187424846(已满)。QQ群进群密码:xttblog,想加微信群的朋友,之前的微信号好友已满,请加博主新的微信号:xttblog,备注:“xttblog”,添加博主微信拉你进群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作可添加助理微信进行沟通!

本文原文出处:业余草: » 面试官让我写一个 ArrayList 的线程不安全的“bug”,并修复它!