集合类不安全问题总结

一、集合类不安全问题之并发修改异常和写时复制处理方案

我们都知道ArrayList是线程不安全的,请编写一个不安全案例,并给出解决办法

package com.jiguiquan.www;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 ** 集合类不安全问题
 * ArrayList:线程不安全
 * @author jiguiquan
 *
 */
public class CollectionNotSafe {
	public static void main(String[] args) {
		List<String> list = new ArrayList<>();
		//使用三个线程,分别向list中添加3组数据
		for (int i = 0; i < 20; i++) {
			new Thread(() -> {
				list.add(UUID.randomUUID().toString().substring(0, 8));
				System.out.println(list);
			},String.valueOf(i)).start();
		}
	}
}

执行结果如下:

image.png

明显地,在多线程环境下,ArrayList的简单add操作就已经开始报异常了,异常类为“ConcurrentModificationException

ConcurrentModificationException:并发修改异常,此异常在多线程集合类操作中很常见,就想空指针异常一样常见;

思考报错问题的思路基本分以下几步走——成为一个优秀的JAVA工程师必不可少的方法论和思维模式:

1、故障现象:java.util.ConcurrentModificationException;

2、导致原因:并发时争抢修改导致,好比一个人正在签名,另一个突然过来抢,那么名册上会出现一个长长的划痕,即并发修改异常;

3、解决方案(千万不要直接回答:加锁):

3.1、使用Vector代替ArrayList:

List<String> list = new Vector<>();

使用Vector可以顺利解决此并发修改问题;

但是我们对比以下Vector和ArrayList后发现:Vector是JDK1.0就出现的集合类,而ArrayList是JDK1.2才出现的,如果Vector很好的话,又不会出现这样的报错,那么又何必再出现ArrayList呢;

原因就在于,Vector是通过增加Synchronized锁的方式实现的线程安全,这种方式对并发的牺牲比较大,这也是为什么我们很少见到Vector的原因所在;

image.png

这里也是我们经常对比ArrayList和Vector的地方:线程安全用Vector,线程不安全用ArrayList;

3.2、使用Collections工具类中的synchronizedList()方法将普通ArrayList包装转化为一个线程安全的加锁的ArrayList;

List<String> list = Collections.synchronizedList(new ArrayList<>());

但是此方法和Vector一样,是通过加锁的方式实现的线程安全,也不是最优的;

3.3、使用JUC包提供的写时复制CopyOnWriteArrayList

List<String> list = new CopyOnWriteArrayList<>();

以上三种解决方案,实际运行结果都如下:

image.png

以上三种方案都可以解决ArrayList的多线程环境下“并发修改异常”;

前两种没什么讲的,我们来看看第三种的源码add方法:

public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

分析:

在执行add操作的时候,即写入的时候,首先先将之前的数组先拷贝一份,然后执行增加元素操作,增加完之后,迅速将之前的数组更新为增加过元素的这个新数组,之前的作废;

4、优化建议(同样的错误不犯第二次):在读多写少的时候推荐使用写时复制 CopyOnWriteArrayList类;


二、集合类不安全值Set

不安全案例:

package com.jiguiquan.www;

import java.util.HashSet;
import java.util.Set;
import java.util.UUID;

/**
 ** 集合类不安全之Set 
 * @author jiguiquan
 *
 */
public class SetNotSafe {
	public static void main(String[] args) {
		Set<String> set = new HashSet<>();
		
		for (int i = 0; i < 20; i++) {
			new Thread(() -> {
				set.add(UUID.randomUUID().toString().substring(0, 8));
				System.out.println(set);
			},String.valueOf(i)).start();
		}
	}
}

执行结果如下:

image.png

显然,跟刚刚的ArrayList报了一样的异常,并发修改异常;

1、故障现象:java.util.ConcurrentModificationException;

2、导致原因:

3、解决办法,和ArrayList类似:

3.1、使用Collections工具类下面的synchronizedSet()方法将普通的HashSet包装转化为线程安全的加锁的HashSet;

Set<String> set = Collections.synchronizedSet(new HashSet<>());

3.2、使用JUC并发包下面的写时复制CopyOnWriteArraySet:

Set<String> set = new CopyOnWriteArraySet<>();

解决方案后的运行结果如下:

image.png


三、集合类不安全之Map

不安全案例:

package com.jiguiquan.www;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
 ** 集合类不安全之Map 
 * @author jiguiquan
 *
 */
public class MapNotSafe {
	public static void main(String[] args) {
		Map<String, String> map = new HashMap<>();
		
		for (int i = 0; i < 30; i++) {
			new Thread(() -> {
				map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0, 8));
				System.out.println(map);
			},String.valueOf(i)).start();
		}
	}
}

执行结果如下:

image.png

显然,跟刚刚的ArrayList报了一样的异常,并发修改异常;

1、故障现象:java.util.ConcurrentModificationException;

2、导致原因:

3、解决办法,和ArrayList类似:

3.1、使用Collections工具类中的synchronizedMap()方法将普通的HashMap包装为线程安全的带锁的Map;

Map<String, String> map = Collections.synchronizedMap(new HashMap<>());

3.2、使用JUC并发包下的ConCurrentHashMap代替普通的HashMap

Map<String, String> map = new ConcurrentHashMap<>();

解决方案后的运行结果如下:

image.png

常见的集合类安全性问题主要也就是ArrayList、HashSet、HashMap这几类;

jiguiquan@163.com

文章作者信息...

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐