美文网首页
(Java) 如何验证 ArrayList 不是线程安全的

(Java) 如何验证 ArrayList 不是线程安全的

作者: jyjz2008 | 来源:发表于2019-05-06 13:04 被阅读0次

    思路

    ArrayList 中的 add(E e) 方法

    java.util.ArrayList 中的 add(E e) 方法里有 size++ 的操作,而 size++ 并不是原子操作,所以如果在 size++ 的过程中,其他线程被调度,并且也去操作同一个 ArrayList,那么 size 的值就可能不准确了。

    实战

    有了这样的思路,我们就可以让一些线程在同一个 ArrayList 上调用 add(E e) 方法。我写了两段示例代码来进行验证。
    首先是让 100 个线程对同一个 ArrayList 执行 add(E e) 方法。

    并发地对 ArrayList 进行添加元素的操作

    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Main {
        public static void main(String[] args) throws Exception {
            // 让 cnt 个线程并发地对 list 进行添加元素的操作
            final int cnt = 100;
            CountDownLatch countDownLatch = new CountDownLatch(cnt);
            ExecutorService executorService = Executors.newCachedThreadPool();
            final int n = 100;
    
            // 令 initialCapacity=cnt * n, 容量肯定够大了, 程序运行过程中就不会出现扩容
            List<Integer> list = new ArrayList<>(cnt * n);
    
            Runnable runnable = () -> {
                for (int i = 0; i < n; i++) {
                    // 添加元素
                    list.add(i);
                    // 尝试让其他线程执行, 让问题更容易暴露出来
                    Thread.yield();
                }
                countDownLatch.countDown();
            };
    
            for (int i = 0; i < cnt; i++) {
                executorService.execute(runnable);
            }
            executorService.shutdown();
            countDownLatch.await();
            System.out.println(list.size());
        }
    }
    

    一次运行结果如下


    最终的 size 可能小于10000

    并发地对 Vector 进行添加元素的操作

    java.util.Vector 是线程安全的,所以如果我们用一些线程并发地对 Vector 进行添加元素的操作,最终的 size 应该不会有问题。
    将上面程序里的 ArrayList 改为 Vector,程序会变为

    import java.util.ArrayList;
    import java.util.List;
    import java.util.Vector;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class Main {
        public static void main(String[] args) throws Exception {
            // 让 cnt 个线程并发地对 list 进行添加元素的操作
            final int cnt = 100;
            CountDownLatch countDownLatch = new CountDownLatch(cnt);
            ExecutorService executorService = Executors.newCachedThreadPool();
            final int n = 100;
    
            // 令 initialCapacity=cnt * n, 容量肯定够大了, 程序运行过程中就不会出现扩容
            List<Integer> list = new Vector<>(cnt * n);
    
            Runnable runnable = () -> {
                for (int i = 0; i < n; i++) {
                    // 添加元素
                    list.add(i);
                    // 尝试让其他线程执行, 让问题更容易暴露出来
                    Thread.yield();
                }
                countDownLatch.countDown();
            };
    
            for (int i = 0; i < cnt; i++) {
                executorService.execute(runnable);
            }
            executorService.shutdown();
            countDownLatch.await();
            System.out.println(list.size());
        }
    }
    

    一次运行结果如下

    最终的 size 是 10000
    为什么并发地对 Vector 执行 add(E e) 操作就没有出现 size 不对的问题呢?
    image.png
    上图是 Vector 源码中的 add(E e) 部分,可见 add(E e) 是一个同步方法,所以 Vector 中的add(E e) 方法相当于一个原子操作。对同一个 Vector 而言,只有一个线程执行完 add(E e) 方法后,其他线程才有可能再次执行 add(E e) 方法。这样 elementCount 的值就不会在 add(E e) 执行期间被弄错。Vectorsize() 方法返回的就是 elementCount 的值
    Vector 的 size() 方法

    相关文章

      网友评论

          本文标题:(Java) 如何验证 ArrayList 不是线程安全的

          本文链接:https://www.haomeiwen.com/subject/eheroqtx.html