我们平常使用HashMap,ArrayList觉得安全是因为是单线程的,它们在多线程下并不安全
JUC并发,在JUC看来,这些集合都是不安全的
- List不安全
单线程的情况下没有问题
package com.wyh.unSafe; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * @program: JUC * @description: 不安全的List * @author: 魏一鹤 * @createDate: 2022-02-14 22:33 **/ public class ListUnSafe { public static void main(String[] args){ //初始化list List<String> list = Arrays.asList("1", "2", "3"); //forEach循环打印 forEach的参数是一个函数式接口 list.forEach(System.out::println); //相当于以下输出写法 //for (String s : list) { // System.out.println(s); //} } }
1
2
3
多线程下发现问题
package com.wyh.unSafe; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; /** * @program: JUC * @description: 不安全的List 多线程 * @author: 魏一鹤 * @createDate: 2022-02-14 22:33 **/ //多线程操作List //并发下的集合都会出这个错 java.util.ConcurrentModificationException 并发修改异常 public class ListUnSafe2 { public static void main(String[] args){ //初始化list 并发下list是不安全的 ArrayList<String> arrayList=new ArrayList<String>(); //循环添加一些字符串 for (int i = 1; i <= 10; i++) { //10个线程去一起并发添加 new Thread(()->{ //uuid随机生成的字符串并且截取前五位字符串 arrayList.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(arrayList); },String.valueOf(i)).start(); } } }
报错
[null, a37dc, 96275]
[null, a37dc, 96275, e061d, 5d2c7, 80856, 2c464, 72ba5, 29bd1, 2ea0a]
[null, a37dc, 96275, e061d, 5d2c7]
[null, a37dc, 96275, e061d, 5d2c7, 80856, 2c464, 72ba5]
[null, a37dc, 96275, e061d, 5d2c7, 80856, 2c464]
Exception in thread "Thread-7" [null, a37dc, 96275, e061d]
[null, a37dc, 96275, e061d, 5d2c7, 80856]
[null, a37dc, 96275]
[null, a37dc, 96275]
java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:911)
at java.util.ArrayList$Itr.next(ArrayList.java:861)
at java.util.AbstractCollection.toString(AbstractCollection.java:461)
at java.lang.String.valueOf(String.java:2994)
at java.io.PrintStream.println(PrintStream.java:821)
at com.wyh.unSafe.ListUnSafe2.lambda$main$0(ListUnSafe2.java:28)
at java.lang.Thread.run(Thread.java:748)
注意这个异常 并发下的集合都会出这个错 java.util.ConcurrentModificationException:并发修改异常
那么既然List并发不安全,我们应该如何解决呢
- 使用Vector
package com.wyh.unSafe; import java.util.*; /** * @program: JUC * @description: 不安全的List 多线程 * @author: 魏一鹤 * @createDate: 2022-02-14 22:33 **/ //多线程操作List //并发下的集合都会出这个错 java.util.ConcurrentModificationException 并发修改异常 public class ListUnSafe2 { public static void main(String[] args){ /** * 那么既然List并发不安全,我们应该如何解决呢 * 1 把ArrayList换成Vector Vector默认就是安全的(不建议使用 因为涉及内存问题) * **/ //初始化list 并发下list是不安全的 但是Vector默认就是安全的 List<String> arrayList = new Vector<>(); //循环添加一些字符串 for (int i = 1; i <= 10; i++) { //10个线程去一起并发添加 new Thread(()->{ //uuid随机生成的字符串并且截取前五位字符串 arrayList.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(arrayList); },String.valueOf(i)).start(); } } }
[4a075, 9cf3d, 0d593]
[4a075, 9cf3d, 0d593, 1a746, 180e5, c0761, ebf2c, eadb4, a43e2, fc728]
[4a075, 9cf3d, 0d593, 1a746, 180e5, c0761, ebf2c, eadb4, a43e2, fc728]
[4a075, 9cf3d, 0d593, 1a746, 180e5, c0761, ebf2c, eadb4, a43e2, fc728]
[4a075, 9cf3d, 0d593, 1a746, 180e5, c0761, ebf2c, eadb4, a43e2]
[4a075, 9cf3d, 0d593, 1a746, 180e5, c0761, ebf2c, eadb4]
[4a075, 9cf3d, 0d593, 1a746, 180e5, c0761, ebf2c]
- 利用Collection工具类来转换安全的List 不止可以生成线程安全的List 还可以生成线程安全的Map Set
[4a075, 9cf3d, 0d593, 1a746, 180e5, c0761]
[4a075, 9cf3d, 0d593, 1a746, 180e5]
[4a075, 9cf3d, 0d593, 1a746]
package com.wyh.unSafe; import java.util.*; /** * @program: JUC * @description: 不安全的List 多线程 * @author: 魏一鹤 * @createDate: 2022-02-14 22:33 **/ //多线程操作List //并发下的集合都会出这个错 java.util.ConcurrentModificationException 并发修改异常 public class ListUnSafe2 { public static void main(String[] args){ /** * 那么既然List并发不安全,我们应该如何解决呢 * 1 把ArrayList换成Vector Vector默认就是安全的(不建议使用 因为涉及内存问题) * 2 利用Collection工具类来转换安全的List 不止可以生成线程安全的List 还可以生成线程安全的Map Set **/ //使用Collections的synchronizedList方法返回一个新的线程安全带的List List<Object> arrayList = Collections.synchronizedList(new ArrayList<>()); //循环添加一些字符串 for (int i = 1; i <= 10; i++) { //10个线程去一起并发添加 new Thread(()->{ //uuid随机生成的字符串并且截取前五位字符串 arrayList.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(arrayList); },String.valueOf(i)).start(); } } }
[de36a, d1853, 1ffee]
[de36a, d1853, 1ffee, 381b9, 838fe, 10cf0, 42b85, 68cce, 1a592]
[de36a, d1853, 1ffee, 381b9, 838fe, 10cf0, 42b85, 68cce]
[de36a, d1853, 1ffee, 381b9, 838fe, 10cf0, 42b85]
[de36a, d1853, 1ffee, 381b9]
[de36a, d1853, 1ffee, 381b9, 838fe, 10cf0]
[de36a, d1853, 1ffee, 381b9, 838fe]
[de36a, d1853, 1ffee, 381b9]
[de36a, d1853, 1ffee]
[de36a, d1853, 1ffee, 381b9, 838fe, 10cf0, 42b85, 68cce, 1a592, 21ee9]
- 以上两种方法属于普通层面的
以下这种属于JUC的,推荐使用JUC下的CopyOnWritrArrayList
package com.wyh.unSafe; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; /** * @program: JUC * @description: 不安全的List 多线程 * @author: 魏一鹤 * @createDate: 2022-02-14 22:33 **/ //多线程操作List //并发下的集合都会出这个错 java.util.ConcurrentModificationException 并发修改异常 public class ListUnSafe2 { public static void main(String[] args){ /** * 那么既然List并发不安全,我们应该如何解决呢 * 1 把ArrayList换成Vector Vector默认就是安全的(不建议使用 因为涉及内存问题) * 2 利用Collection工具类来转换安全的List 不止可以生成线程安全的List 还可以生成线程安全的Map Set * 3 使用JUC下的CopyOnWriteArrayList 当然也有线程安全的CopyOnWriteArraySet **/ //使用JUC下的CopyOnWriteArrayLis 它是一个线程安全的List //CopyOnWrite:写入时复制 又叫COW(CopyOnWrite)思想 它是计算机设计领域的一种优化策略 //在写入的时候避免覆盖,造成数据问题 读写分离 写入的时候先复制出来,写入完之后再插入进去 保证线程安全 List<Object> arrayList =new CopyOnWriteArrayList<>(); //循环添加一些字符串 for (int i = 1; i <= 10; i++) { //10个线程去一起并发添加 new Thread(()->{ //uuid随机生成的字符串并且截取前五位字符串 arrayList.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(arrayList); },String.valueOf(i)).start(); } } }
[3837d, 70019, bf585, 4d62a, 7575d]
[3837d, 70019, bf585, 4d62a, 7575d, a4ae9, 34025, 3b3d0]
[3837d, 70019, bf585, 4d62a, 7575d, a4ae9, 34025]
[3837d, 70019, bf585, 4d62a]
[3837d, 70019, bf585, 4d62a, 7575d, a4ae9]
[3837d, 70019, bf585, 4d62a]
[3837d, 70019, bf585, 4d62a]
[3837d, 70019, bf585, 4d62a, 7575d, a4ae9, 34025, 3b3d0]
[3837d, 70019, bf585, 4d62a, 7575d, a4ae9, 34025, 3b3d0, 5edaa, 45311]
[3837d, 70019, bf585, 4d62a, 7575d, a4ae9, 34025, 3b3d0, 5edaa]
那么为什么在并发的情况下使用CopyOnWritrArrayList就线程安全呢?
通过查看源码发现它也是使用一个数组,不过这个数组是被transient(有序的)和volatile(唯一的 )进行修饰的