目录
- 二、集合类不安全
- 2.1ArrayList线程不安全
- 2.1.1例子
- 2.1.2解决ArrayList多线程不安全
- 2.2HashSet线程不安全
- 2.2.1例子
- 2.2.2解决HashSet多线程不安全
- 2.3HashMap线程不安全
- 2.3.1例子
- 2.3.2解决HashMap多线程不安全
二、集合类不安全
2.1ArrayList线程不安全
2.1.1例子
单线程
public class NotSafeDemo {
//单线程很安全
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c");
list.forEach(System.out::println);
}
}
a
b
c
多线程
public class NotSafeDemo {
//多线程不安全
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i < 50; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
//报错java.util.ConcurrentModificationException并发修改异常
多线程下ArrayList报错是因为:ArrayList是线程不安全类,通过源码可以看出其add方法没有加锁,表示同一时刻
可以有多个线程操作资源类
,不安全
2.1.2解决ArrayList多线程不安全
方法一:使用Vector类(不推荐)
public class NotSafeDemo {
//多线程不安全
public static void main(String[] args) {
List<String> list = new Vector<>();
for (int i = 0; i < 50; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
通过源码我们可以看到Vector类的add方法加了锁所以是安全的
方法二:Collections工具类
public class NotSafeDemo {
//多线程不安全
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<String>());
for (int i = 0; i < 50; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
方法三:使用JUC中CopyOnWriteArrayList类(推荐)
CopyOnWriteArrayList在java.util.concurrent包中
public class NotSafeDemo {
//多线程不安全
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 50; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
除此之外,CopyOnWriteArrayList包中还有很多常用的线程安全的类
为什么CopyOnWriteArrayList可以解决线程安全问题呢?
通过源码可以看出CopyOnWriteArrayList的add方法添加了可重入锁
写入时复制(CopyOnWrite)思想:
- 写入时复制(CopyOnWrite,简称COW)思想是计算机程序设计领域中的一种优化策略。其核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。
读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array
CopyOnWriteArrayList为什么并发安全且性能比Vector好:
- Vector是增删改查方法都加了synchronized,保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降,而CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性能就好于Vector,CopyOnWriteArrayList支持读多写少的并发情况。
2.2HashSet线程不安全
2.2.1例子
public class NotSafeDemo {
//多线程不安全
public static void main(String[] args) {
Set<String> set = new HashSet<>();
for (int i = 0; i < 50; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
2.2.2解决HashSet多线程不安全
方法一:Collections工具类
public class NotSafeDemo {
//多线程不安全
public static void main(String[] args) {
Set<String> set = Collections.synchronizedSet(new HashSet<>());
for (int i = 0; i < 50; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
方法二:使用CopyOnWriteArraySet类
public class NotSafeDemo {
//多线程不安全
public static void main(String[] args) {
CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet<>();
for (int i = 0; i < 50; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,8));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
2.3HashMap线程不安全
2.3.1例子
public class NotSafeDemo {
//多线程不安全
public static void main(String[] args) {
HashMap<String, String> map = new HashMap<>();
for (int i = 0; i < 50; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
2.3.2解决HashMap多线程不安全
方法一:Collections工具类
public class NotSafeDemo {
//多线程不安全
public static void main(String[] args) {
Map<String, String> map = Collections.synchronizedMap(new HashMap<String, String>());
for (int i = 0; i < 50; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
方法二:使用ConcurrentHashMap类
public class NotSafeDemo {
//多线程不安全
public static void main(String[] args) {
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
for (int i = 0; i < 50; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,8));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}