java常见面试考点
往期文章推荐:
java常见面试考点(二十一):单点登录
java常见面试考点(二十二):购物车实现
java常见面试考点(二十三):消息队列
java常见面试考点(二十四):强引用,软引用,虚引用,弱引用
java常见面试考点(二十五):CAS是什么
【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权);
本博客的内容来自于:java常见面试考点(二十六):集合类不安全;
学习、合作与交流联系q384660495;
本博客的内容仅供学习与参考,并非营利;
文章目录
一、集合类不安全问题之并发修改异常
多线程环境下,对同一个集合进行操作可能会出现异常,代码如下:
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. 值覆盖问题,因为ArrayList底层是数组,写入值的时候要先计算到一个下标位置,然后去赋值,多线程可能会获得同一个下标,发生值覆盖问题。
2. 空指针异常,因为ArrayList底层是数组,写入值在数组满时需要扩容,在扩容还没有完成的时候,新的下标已经计算出来并要去插入,那么就会出现空指针异常。 - 有的读有的写:不仅包含了情况2的所有问题,此外还有:
1. 如果多线程有的读有的写,对于ArrayList底层,某些情况下,对象是不允许修改的,如果修改了,后面调用某些方法时,然后就会抛出ConcurrentModificationException
2. 具体一下,因为源码里,写操作对集合修改是写,而next,remove等Itr的遍历操作的时候会通过当前集合的修改次数与Itr对象创建时记录的次数校验是否被修改,如果不一致则说明还有别的线程再改,就会抛出异常。
3. JDK作者说了,会抛出这个异常的叫做fail-fast iterator
第三种情况其实就是上面问题的产生原因。
二、集合类不安全问题之List
- 使用List接口下的实现集合Vector 类,但是该类是JDK1.0出现的,其add()是syncronized修饰的。数据一致性可以得到保证,但是并发性会下降。
- 数据量小的时候,使用工具类 Collections中的synchronizedList将其变为安全的集合类,其中还有构建set,map集合安全类的方法。
- JUC包中的CopyOnWriteArrayList,推荐使用
详解CopyOnWriteArrayList
CopyOnWriteArrayList写时复制容器。 往一个容器添加元素的时候,不直接往当前容器 Object[] 添加, 而是先将当前容器 Object[] 进行copy, 复制出一个新的容器 Object[] newElements, 然后往新的容器Object[] newElements里添加元素, 添加完元素之后, 再将原容器的引用指向新的容器 setArray(newElements)。这样做的好处是可以对 CopyOnWrite容器进行并发的读, 而不需要加锁, 因为当前容器不会添加任何元素. 所以 CopyOnWrite容器 也是一种读写分离的思想, 读和写不同的容器。源码如下:
三、集合类不安全问题之Set
HashSet底层数据结构是HashMap(源码构造器里面 new HashMap()),其add方法实际上return map.put(e,PRESENT)==null; PRESENT实际上就是一个object常量,所以实际上就是HashMap的keys。
- 使用工具类 Collections => Set set = Collections.synchronizedSet(new HashSet<>());
- JUC包 中的Set set = new CopyOnWriteArraySet<>();
详解CopyOnWriteArraySet
CopyOnWriteArraySet和CopyOnWriteArrayList类似,其实CopyOnWriteSet底层包含一个CopyOnWriteList,几乎所有操作都是借助CopyOnWriteList实现的,就像HashSet包含HashMap。而CopyOnWriteArrayList本质是个动态数组队列,所以CopyOnWriteArraySet相当于通过通过动态数组实现的“集合”! CopyOnWriteArrayList中允许有重复的元素;但是,CopyOnWriteArraySet是一个集合,所以它不能有重复集合。因此,CopyOnWriteArrayList额外提供了addIfAbsent()和addAllAbsent()这两个添加元素的API,通过这些API来添加元素时,只有当元素不存在时才执行添加操作!至于CopyOnWriteArraySet的“线程安全”机制,和CopyOnWriteArrayList一样,是通过volatile和互斥锁来实现的。
四、集合类不安全问题之Map
hashmap出现的问题比较复杂,参考这篇文章HashMap线程不安全的体现
- 使用Collections工具类的synchronizedMap()方法
- 使用HashTable,但HashTable也是一个古老的技术,它的所有方法也是都加了锁,并发性不好,不推荐使用。独占锁。
- 使用ConcurrentHashMap(J.U.C提供),java没有为map提供写时复制的类,所以我们使用ConcurrentHashMap来解决map类线程安全的问题。ConcureentHashMap同步容器类是java5增加的一个线程安全的哈希表。对于多线程的操作,介于HashMap与Hashtable之间。内部采用“锁分段”机制替代Hashtable的独占锁。进而提高性能。
参考这篇文章ConcurrentHashMap底层实现原理(JDK1.7 & 1.8)和ConcureentHashMap详解