并发下的ArrayList

  1. package com.demo.base;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. public class ThreadDemo {
  5. public static void main(String[] args) throws InterruptedException {
  6. MyRunnable myRunnable = new MyRunnable();
  7. Thread t1 = new Thread(myRunnable);
  8. Thread t2 = new Thread(myRunnable);
  9. t1.start();
  10. t2.start();
  11. t1.join();
  12. t2.join();
  13. System.out.println("list 的大小为: " + myRunnable.list.size());
  14. }
  15. }
  16. class MyRunnable implements Runnable {
  17. List<Integer> list = new ArrayList<>();
  18. @Override
  19. public void run() {
  20. for (int i=0; i<10000; i++){
  21. list.add(i);
  22. }
  23. }
  24. }

注意:

ArrayList 是一个线程不安全的容器,在多线程下,ArrayList 可能会有以下结果:

  • 程序正常结束,ArrayList 的大小为 20000,这说明并发程序即使有问题,也不是每次都会表现出来。
  • 程序正常结束,ArrayList 的大小小于 20000,这是由于多线程访问冲突,多个线程同时对 ArrayList 的同一位置进行赋值导致的。
  • 程序抛出数组下标越界的异常

    Exception in thread “Thread-0” java.lang.ArrayIndexOutOfBoundsException: 244

这是因为 ArrayList 在扩容过程中,内部一致性被破坏,但由于没有锁的保护,另一个线程访问到了不一致的内部状态,导致出现越界问题。

解决方法

  • 使用线程安全的 Vector 代替 ArrayList
  • 在向 ArrayList 中添加元素时,加锁
  • 使用 JUC 包下的安全容器 CopyOnWriteArrayList 代替 ArrayList

    并发下的HashMap

    ```java package com.demo.base;

import java.util.HashMap; import java.util.Map;

public class ThreadDemo { static Map map = new HashMap<>();

  1. public static void main(String[] args) throws InterruptedException {
  2. Thread t1 = new Thread(new MyRunnable());
  3. Thread t2 = new Thread(new MyRunnable());
  4. t1.start();
  5. t2.start();
  6. t1.join();
  7. t2.join();
  8. System.out.println(map.size());
  9. }
  10. static class MyRunnable implements Runnable{
  11. @Override
  12. public void run() {
  13. for(int i=0; i<10000; i++){
  14. map.put(Thread.currentThread().getName() + i, i + "");
  15. }
  16. }
  17. }

} ```

注意:

运行这段代码,也可能得到3个结果:

  1. 程序正常结束,HashMap的最终大小为20000。
  2. 程序正常结束,但HashMap的大小小于20000。这两点跟ArrayList问题类似。
  3. 程序抛出异常

image.png
这个异常是在jdk8中会出现的,原因是HashMap的数据结构是数组和链表组成的,当链表在达到阈值时,链表结构(Node类型节点)会转换成红黑树结构(TreeNode类型节点)。于是当一个线程将Node类型节点转换成TreeNode类型节点并平衡红黑树时,另一个线程执行put方法并正好将数据放到同一个数组格子中时,先会插入Node类型的数据,这将导致前一个线程平衡红黑树时在转换数据类型时发生异常。