ArrayList
问题
线程不安全,报错:java.util.ConcurrentModificationException
package top.ygy.thread;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @Description: TODO(集合类不安全问题)
* @author yangguangyuan
* @date 2019年6月17日
*
*/
public class ContainerNotSafeDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}).start();
}
}
}
分析
ArrayList线程不安全,因为add方法为了保证并发性,没有加锁
并发争抢修改导致,参考花名册情况.一个人正在写入,另一个人过来抢夺,导致数据不一致,并发修改异常.
解决
Vector
Vector的add方法有synchronized锁,可以保证线程安全,但是性能降低,Vector是jdk1.0,ArrayList的版本在1.0之后,所以ArrayList的解决办法不单单是添加锁
//方案一:Vector
public void test2() {
List<String> list = new Vector<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}).start();
}
}
Collections
集合操作工具类
//方案二:
public void test3() {
List<Object> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}).start();
}
}
CopyOnWriteArrayList
解决
//方案三
public void test4() {
List<Object> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
list.add(UUID.randomUUID().toString().substring(0, 8));
System.out.println(list);
}).start();
}
}
原理
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);//将引用指向新数组 1
return true;
} finally {
lock.unlock();//解锁啦
}
}
add()
在添加集合的时候加上了锁,保证了同步,避免了多线程写的时候会Copy出N个副本出来。写时复制,正在写的时候,使用的还是旧的数据,写入完成之后,将指针指向新的数据
优缺点
- 优点
- 保证了数据一致性完整
- 解决了
像ArrayList
、Vector
这种集合多线程遍历迭代问题,记住,Vector
虽然线程安全,只不过是加了synchronized
关键字,迭代问题完全没有解决!
- 缺点
- 耗内存(集合复制)
- 实时性不高
使用场景
- 读多写少(白名单,黑名单,商品类目的访问和更新场景),为什么?因为写的时候会复制新集合
- 集合不大,为什么?因为写的时候会复制新集合
- 实时性要求不高,为什么,因为有可能会读取到旧的集合数据
Set
问题
线程不安全,报错:java.util.ConcurrentModificationException
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString());
System.out.println(set.toString());
}, "T1").start();
}
}
分析
一个线程正在写,另一个线程过来抢占资源,会造成数据不一致,进而报并发修改异常。
解决
Collections
//解决一
public void test2() {
Set<String> set = Collections.synchronizedSet(new HashSet<>());
for (int i = 0; i < 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString());
System.out.println(set.toString());
}, String.valueOf(i)).start();
}
}
CopyOnWriteArraySet
//解决二
public void test3() {
Set<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 30; i++) {
new Thread(() -> {
set.add(UUID.randomUUID().toString());
System.out.println(set.toString());
}, String.valueOf(i)).start();
}
}
Map
问题
public void test1() {
Map<String, String> map = new HashMap<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(),
UUID.randomUUID().toString().substring(0, 8));
System.out.println(map);
},String.valueOf(i)).start();
}
}
分析
一个线程正在写,另一个线程过来抢占资源,会造成数据不一致,进而报并发修改异常。
解决
Collections
public void test2() {
Map<String, String> map = Collections.synchronizedMap(new HashMap<>());
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(),
UUID.randomUUID().toString().substring(0, 8));
System.out.println(map);
},String.valueOf(i)).start();
}
}
ConcurrentHashMap
public void test3() {
Map<String, String> map = new ConcurrentHashMap<>();
for (int i = 1; i <= 30; i++) {
new Thread(() -> {
map.put(Thread.currentThread().getName(),
UUID.randomUUID().toString().substring(0, 8));
System.out.println(map);
},String.valueOf(i)).start();
}
}