不安全的集合类
在并发环境下
,我们经常使用的集合类(List、Map、Set)其实都是不安全的!
List
List在单线程的情况下是安全的,但是多线程的情况下是不安全的,我们来看两段代码:
单线程
public class UnsafeList1 {
public static void main(String[] args) {
List<String> list= Arrays.asList("a","b","c");
list.forEach(System.out::println);
}
}
多线程
public class UnsafeList2 {
public static void main(String[] args) {
ArrayList<Object> list=new ArrayList<>();
for (int i = 1; i <=30; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
通过以上两段代码,我们可以看到,在多线程情况下会报ConcurrentModificationException(并发修改)
异常,那我们如何去保证在多线程的情况下List的安全了,有以下三中方法:
- 使用Vector,我们都知道Vector是线程安全的。
public class UnsafeList2 {
public static void main(String[] args) {
List<String> list=new Vector<>();
for (int i = 1; i <=30; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
2.利用Collections类下的方法synchronizedList
,将其变为安全的集合类。
public class UnsafeList2 {
public static void main(String[] args) {
List<String> list= Collections.synchronizedList(new ArrayList<>());
for (int i = 1; i <=30; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
3. 使用CopyOnWriteArrayList。
public class UnsafeList2 {
public static void main(String[] args) {
List<String> list=new CopyOnWriteArrayList<>();
for (int i = 1; i <=30; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
三种方式比较:
Vector
我们首先看下Vector和ArrayList的源码,
通过源码我们知道Vector在JDK1.0的时候就已经有了,而ArrayList是在JDK1.2才有的,不知道大家有没有想过这样一个问题,Vector既然已经处理了线程不安全的问题,为什么还有添加一个不安全的ArrayList呢?其实是有原因的,虽然实现了List接口,底层都是数组,但是Vector的性能却比ArrayList低。所以我们一般的话不采用Vector。
synchronizeList
通过源码分析,它是通过给自己本身暴力加锁而实现线程安全的,所以效率也比较低下,但是相对于Vector高点。
CopyOnWriteArrayList
CopyOnWriteArrayList其实利用了计算机领域的一个COW思想,多个调用者,想调用相同的资源,改变指针指向;在多线程下如果只是去读,就不会产生锁! 假如你是去写,就需要拷贝一份都自己哪里,修改完毕后,再替换指针!这样写的话保证数据的安全,效率也高,推荐使用!
Map
还是首先来一段代码:
public class UnsafeMap {
public static void main(String[] args) {
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,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
我们可以看到,HashMap是不安全的,解决方案就是使用HashTablehe和ConcurrentHashMap。HashTable效率低下,不建议使用!
Set
public class UnsafeSet1 {
public static void main(String[] args) {
Set<String> set =new HashSet<>();
for (int i = 1; i <=30 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
Set的底层是HashMap,HashMap是不安全的,所以Set自然也是线程不安全的。解决方案就是synchronizedSet和CopyOnWriteSet。