2022.8.21 集合类不安全
6、集合类不安全
1、并发下ArrayList 不安全
1 package com.xing.Unsafe; 2 3 import java.util.*; 4 import java.util.concurrent.CopyOnWriteArrayList; 5 6 public class ListTest { 7 public static void main(String[] args) { 8 //并发下ArrayList是不安全的 会报错 9 10 // List<String> list = new ArrayList<>(); 11 // for (int i =0; i< 10; i++){ 12 // new Thread(()->{ 13 // list.add(UUID.randomUUID().toString().substring(0,5)); 14 // System.out.println(list); 15 // },String.valueOf(i)).start(); 16 // } 17 18 /** 19 * 解决方案: 20 * 1.List<String> list = new Vector<>(); 21 * 22 *集合的顶级:Collections 23 * 2.List<String> list = Collections.synchronizedList(Arrays.asList("1", "2", "3")); 24 * 25 * 3.List<String> list = new CopyOnWriteArrayList<>(); 26 */ 27 /** CopyOnWriteArrayList 写入时复制 COW:计算机程序设计领域的一种优化策略; 28 * 多个线程调用时,list读取的时候是固定的,写入(存在覆盖) 29 * 在写入的时候避免覆盖,造成数据问题 30 31 * CopyOnWriteArrayList 比 Vector 好在哪里? Vector使用了synchronized 效率低 32 * 写入的时候复制一份 33 */ 34 35 List<String> list = new CopyOnWriteArrayList<>(); 36 for (int i =0; i< 10; i++){ 37 new Thread(()->{ 38 list.add(UUID.randomUUID().toString().substring(0,5)); 39 System.out.println(list); 40 },String.valueOf(i)).start(); 41 } 42 } 43 } 44
总结:多个线程调用的时候,List读取的时候是固定的,写入时可能会被覆盖,那么就要先CopyOnWrite写入时复制一份出来调用,调用写完再放回去,再写入的时候避免覆盖,造成数据问题。
CopyOnWriteArrayList 比 Vector牛在哪里
Vector 的 add方法源码:
1 // Vector 的 add方法 2 public synchronized boolean add(E e) { 3 modCount++; 4 ensureCapacityHelper(elementCount + 1); 5 elementData[elementCount++] = e; 6 return true; 7 }
Vector用了sychronized同步方法(同步方法自动加锁解锁)效率会很多
CopyOnWriteArrayList 的add方法源码:
1 //CopyOnWriteArrayList 的add方法 2 3 public boolean add(E e) { 4 final ReentrantLock lock = this.lock; 5 lock.lock(); 6 try { 7 Object[] elements = getArray(); 8 int len = elements.length;//取出长度 9 Object[] newElements = Arrays.copyOf(elements, len + 1);//拿出复制 10 newElements[len] = e;//并且修改 11 setArray(newElements);//让后将数据set回去 12 return true; 13 } finally { 14 lock.unlock(); 15 } 16 }
总结:CopyOnWriteArrayList用的是写入时候复制之后再set回去效率而高出很多
2、并发下Set不安全
1 package com.xing.Unsafe; 2 3 import java.util.Set; 4 import java.util.UUID; 5 import java.util.concurrent.CopyOnWriteArraySet; 6 7 public class SetTest { 8 public static void main(String[] args) { 9 //ConcurrentModificationException 并发修改异常 10 /* 11 有问题:与List一样报错 Set<String> strings = new HashSet<>(); 12 解决方案:Set<String> strings1 = Collections.synchronizedSet(new HashSet<String>()); 13 最佳解决方案:Set<String> strings = new CopyOnWriteArraySet<>(); 14 */ 15 Set<String> strings = new CopyOnWriteArraySet<>(); 16 for (int i = 0; i < 30; i++) { 17 new Thread(()->{ 18 strings.add(UUID.randomUUID().toString().substring(0, 5)); 19 System.out.println(strings); 20 },String.valueOf(i)).start(); 21 } 22 } 23 } 24
HashSet源码
1 //HashSet源码: 2 3 public HashSet() { 4 map = new HashMap<>(); 5 } 6 7 8 //HashSet本质就是 map key是无法重复的 9 public boolean add(E e) { 10 return map.put(e, PRESENT)==null; 11 } 12 13 14 private static final Object PRESENT = new Object();//不变的值
3、HashMap不安全
1 package com.xing.Unsafe; 2 3 import java.util.Map; 4 import java.util.UUID; 5 import java.util.concurrent.ConcurrentHashMap; 6 7 public class MapTest { 8 public static void main(String[] args) { 9 // HashMap<String, String> hashMap = new HashMap<>(); 10 //map是这样用的么? 不是,工作中不用HashMap 11 12 13 //默认等价于什么? new HashMap<>(16,0.75) 第一个为初始化容量,第二个参数为默认的加载因子 14 //Map<String,String> map = new HashMap<>();//线程不安全 15 16 17 Map<String,String> map = new ConcurrentHashMap<>();//安全 18 19 for (int i = 0; i<=30; i++){ 20 new Thread(()->{ 21 map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5)); 22 System.out.println(map); 23 },String.valueOf(i)).start(); 24 } 25 26 } 27 } 28
7、Callable
-
可以有返回值
-
可以抛出异常
-
方法不同 run() / call()
Thread():参数只能是Runnable,怎么与Callable挂上勾?通过Runnable
FutureTask是个适配类,这里可以看到可以通过FutureTask可以两边互相勾搭使用,Runnavle又和Thread有关系
1 package com.xing.Unsafe; 2 3 import java.util.concurrent.Callable; 4 import java.util.concurrent.ExecutionException; 5 import java.util.concurrent.FutureTask; 6 7 public class CallableTest { 8 public static void main(String[] args) throws ExecutionException, InterruptedException { 9 // new Thread(new Runnable()).start(); 10 //Runnable接口的实现类为FutureTask 11 //new Thread(new FutureTask<>(Callable)).strat(); 12 13 14 MyThread myThread = new MyThread(); 15 16 //适配类,使Callable可以被调用 17 FutureTask futureTask = new FutureTask<>(myThread); 18 19 //两个线程执行会打印几个call 20 //一个,结果会被缓存,提高效率,因此只打印一个call 21 new Thread(futureTask,"A").start(); 22 new Thread(futureTask,"B").start(); 23 24 // 获取Callable的返回结果 25 // get方法可能会产生阻塞,把它放到最后,或者使用异步通信 26 Integer integer = (Integer)futureTask.get(); 27 System.out.println(integer); 28 } 29 } 30 31 /* 32 class MyThread implements Runnable{ 33 @Override 34 public void run() { 35 36 } 37 }*/ 38 39 //<Integer> 代表所需要的返回值 40 class MyThread implements Callable<Integer>{ 41 @Override 42 public Integer call() throws Exception { 43 System.out.println("call()"); 44 return 1024; 45 } 46 } 47