集合类的一些并发操作问题

线程不安全的集合类:

  1. ArrayList()
  2. LinkedList()
  3. HashSet()
  4. HashMap()
  5. TreeSet()
  6. TreeMap()

并发操作集合类:

ArrayList示例代码:

/**
 * 集合类不安全的问题
 * ArrayList
 */
public class ContainerNotSafeDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString());
                System.out.println(list);
            }, String.valueOf(i + 1)).start();
        }
    }
}

100个线程同时向同一个List中添加元素时,系统可能会报出java.util.ConcurrentModificationException这个并发修改集合的错误。

解决方案:
  • 使用new Vector<>()类,不建议,因为底层使用了synchronized,性能低
  • 使用Collections.synchronizedList(new ArrayList<>()),是Collections工具类提供的方法
  • 使用new CopyOnWriteArrayList<>(),推荐使用,采用写时复制的方式,解决并发问题,在写的时候不影响其他线程对集合的读操作。
new Vector<>()源码:
    /**
     * Appends the specified element to the end of this Vector.
     *
     * @param e element to be appended to this Vector
     * @return {@code true} (as specified by {@link Collection#add})
     * @since 1.2
     */
    public synchronized boolean add(E e) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

这个类的底层大量使用了synchronized关键字。

new CopyOnWriteArrayList<>()源码:
    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return {@code true} (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
    	// 可重入锁
        final ReentrantLock lock = this.lock;
        // 加锁
        lock.lock();
        try {
        	// 获取集合
            Object[] elements = getArray();
            // 获取集合的长度
            int len = elements.length;
            // 复制一个数组并将长度加1
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            // 将元素添加到新数组的最后
            newElements[len] = e;
            // 将数组的引用指向新数组
            setArray(newElements);
            return true;
        } finally {
        	// 最后解锁
            lock.unlock();
        }
    }
        /**
     * Gets the array.  Non-private so as to also be accessible
     * from CopyOnWriteArraySet class.
     */
    final Object[] getArray() {
    	// 获取数组
        return array;
    }
    /** The array, accessed only via getArray/setArray. */
    // 使用volatile 定义的数组
    private transient volatile Object[] array;
    /**
     * Sets the array.
     */
    final void setArray(Object[] a) {
    	// 将array的引用指向a数组
        array = a;
    }

CopyOnWrite容器即写时复制容器

源码中add的执行过程:
往一个容器添加元素的时候,不直接往当前容器Object[]添加,而是先将容器Object[]进行Copy,复制出一个新的容器Object[] newElements,然后往新的容器Object[] newElement中添加元素,添加完元素之后,再将原容器的引用指向新的容器setArray(newElement)

好处:
可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写在不同的容器。

HashSet示例代码:

/**
 * 集合类不安全的问题
 * ArrayList
 */
public class ContainerNotSafeDemo {
    public static void main(String[] args) {
        Set<String> set = new HashSet<>();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString());
                System.out.println(set);
            }, String.valueOf(i + 1)).start();
        }
    }
}

ArrayList相同,100个线程同时修改一个Set集合,也出现了java.util.ConcurrentModificationException错误。

解决方案:

  • 使用Collections.synchronizedSet(new HashSet<>()),是Collections工具类提供的方法。
  • 使用new CopyOnWriteArraySet<>(),其底层的实现是使用new CopyOnWriteArrayList<>()
HashSet源码:
    /**
     * Constructs a new, empty set; the backing <tt>HashMap</tt> instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<>();
    }

Hash的底层使用的是HashMap

	// Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();
    /**
     * Adds the specified element to this set if it is not already present.
     * More formally, adds the specified element <tt>e</tt> to this set if
     * this set contains no element <tt>e2</tt> such that
     * <tt>(e==null&nbsp;?&nbsp;e2==null&nbsp;:&nbsp;e.equals(e2))</tt>.
     * If this set already contains the element, the call leaves the set
     * unchanged and returns <tt>false</tt>.
     *
     * @param e element to be added to this set
     * @return <tt>true</tt> if this set did not already contain the specified
     * element
     */
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

HashSet的元素是HashMapkeyHashMapvalue是一个Object类型的常量PRESENT

CopyOnWriteArraySet源码
    /**
     * Creates an empty set.
     */
    public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }

CopyOnWriteArraySet的底层使用的是CopyOnWriteArrayList

HashMap示例代码:

/**
 * 集合类不安全的问题
 * HashMap
 */
public class ContainerNotSafeDemo {
    public static void main(String[] args) {
        Map<String, String> map = new HashMap<>();
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString());
                System.out.println(map);
            }, String.valueOf(i + 1)).start();
        }
    }
}

运行代码也会抛出相同的错误java.util.ConcurrentModificationException

解决方案:

  • 使用工具类Collections提供的方法synchronizedMap(new HashMap<>())
  • 使用new ConcurrentHashMap<>()ConcurrentHashMap底层使用的是分段锁,相对于锁整个map,分段锁具有更高的并发性。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值