juc学习四(集合不安全问题)
在并发环境下
,我们经常使用的集合类(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) { List<String> list=new ArrayList<>(); for (int i = 1; i <=50; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); },String.valueOf(i)).start(); } } }
通过以上两段代码,我们可以看到,在多线程情况下会报java.util.ConcurrentModificationException并发修改
异常。
而ArrayList是jdk1.2出现的,是一个不加锁的集合类,并发性上升,但是牺牲多线程的安全性为代价。再看ArrayList源码发现add方法没有加锁,所有多线程情况下出现线程异常问题。
解决方案
1 使用List接口下的实现集合Vector 类,但是该类是JDK1.0出现的,其add()是syncronized修饰的。数据一致性可以得到保证,但是并发性会下降。
.
2 使用工具类 Collections中的synchronizedList将其变为安全的集合类,其中还有构建set,map集合安全类的方法。
public class UnsafeList4 { public static void main(String[] args) { //构建一个同步的synchronizedList List<String> list= Collections.synchronizedList(new ArrayList<>()); for (int i = 1; i <=50; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); },String.valueOf(i)).start(); } } }
3 JUC包中的CopyOnWriteArrayList,推荐使用
public class UnsafeList5 { public static void main(String[] args) { //写时复制容器 List<String> list= new CopyOnWriteArrayList<>(); for (int i = 1; i <=50; i++) { new Thread(()->{ list.add(UUID.randomUUID().toString().substring(0,5)); System.out.println(list); },String.valueOf(i)).start(); } } }
CopyOnWriteArrayList写时复制容器。 往一个容器添加元素的时候,不直接往当前容器 Object[] 添加, 而是先将当前容器 Object[] 进行copy, 复制出一个新的容器 Object[] newElements, 然后往新的容器Object[] newElements里添加元素, 添加完元素之后, 再将原容器的引用指向新的容器 setArray(newElements)。这样做的好处是可以对 CopyOnWrite容器进行并发的读, 而不需要加锁, 因为当前容器不会添加任何元素. 所以 CopyOnWrite容器 也是一种读写分离的思想, 读和写不同的容器。
CopyOnWriteArrayList的add源码,底层volatile Object[] array,add方法里面加了锁ReentrantLock
写入时复制(COW)思想原理:指针,复制指向的问题
集合的线程不安全问题之Set
HashSet底层数据结构是HashMap(源码构造器里面 new HashMap()),其add方法实际上return map.put(e,PRESENT)==null; PRESENT实际上就是一个object常量,所以实际上就是HashMap的keys。
解决方案
1 使用工具类 Collections => Set<Object> set = Collections.synchronizedSet(new HashSet<>());
2 JUC包 中的Set<Object> set = new CopyOnWriteArraySet<>();
CopyOnWriteArraySet和CopyOnWriteArrayList类似,其实CopyOnWriteSet底层包含一个CopyOnWriteList,几乎所有操作都是借助CopyOnWriteList实现的,就像HashSet包含HashMap。而CopyOnWriteArrayList本质是个动态数组队列,所以CopyOnWriteArraySet相当于通过通过动态数组实现的“集合”! CopyOnWriteArrayList中允许有重复的元素;但是,CopyOnWriteArraySet是一个集合,所以它不能有重复集合。因此,CopyOnWriteArrayList额外提供了addIfAbsent()和addAllAbsent()这两个添加元素的API,通过这些API来添加元素时,只有当元素不存在时才执行添加操作!至于CopyOnWriteArraySet的“线程安全”机制,和CopyOnWriteArrayList一样,是通过volatile和互斥锁来实现的。
集合的线程不安全问题之Map
Map集合仍然会出现上述的java.util.ConcurrentModificationException异常。
解决方案:
1.使用Collections工具类的synchronizedMap()方法
2.使用HashTable
但HashTable也是一个古老的技术,它的所有方法也是都加了锁,并发性不好,不推荐使用。独占锁。
3.使用ConcurrentHashMap(J.U.C提供)
java没有为map提供写时复制的类,所以我们使用ConcurrentHashMap来解决map类线程安全的问题。ConcureentHashMap同步容器类是java5增加的一个线程安全的哈希表。对于多线程的操作,介于HashMap与Hashtable之间。内部采用“锁分段”机制替代Hashtable的独占锁。进而提高性能。
示例代码:
public class UnsafeMap { public static void main(String[] args) { //hashMap(); // collections(); concurrentHashMap(); } //多线程下HashMap线程不安全,出现并发修改异常 public static void hashMap(){ Map<String,Object> map= new HashMap<>(); for (int i = 1; i <=50; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5)); System.out.println(map); },String.valueOf(i)).start(); } } //使用Collections工具类中的synchronizedMap构建安全map public static void collections(){ Map<String,Object> map= Collections.synchronizedMap(new HashMap<>()); for (int i = 1; i <=50; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5)); System.out.println(map); },String.valueOf(i)).start(); } } //使用ConcurrentHashMap public static void concurrentHashMap(){ Map<String,Object> map= new ConcurrentHashMap<>(); for (int i = 1; i <=50; i++) { new Thread(()->{ map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5)); System.out.println(map); },String.valueOf(i)).start(); } } }
ConcurrentHashMap底层实现原理可以仔细阅读下面这篇博客,博主写的特详细
https://www.jianshu.com/p/865c813f2726