带着问题来阅读html
一、Java有哪些集合java
二、不一样集合的应用场景分别是哪些编程
三、哪些实现类是线程安全的segmentfault
四、为何Java集合不能存放基本类型数组
五、集合的fail-fast和fail-safe是什么安全
Java经过Java Collections Framework(JCF)为开发者提供了一系列集合接口和实现,所谓集合,就是多个Java对象的汇集。数据结构
学习过数据结构的同窗们对各种集合的定义确定不陌生,Java经过提供一系列的内置数据结构实现,为开发者提升了开发的便利性,提高了程序的兼容性,下降了编程的复杂性。并发
图片出自https://www.pdai.tech/md/java/collection/java-collection-all.html框架
Java集合包含两个顶层接口:Collection和Map,Collection是主要保存对象的集合,Map是保存键值对的集合。学习
下面咱们对两类集合的实现作简单介绍。
Collection是Jdk1.2版本引入的接口,用于描述保存对象的集合,它的扩展接口有List、Queue、Set。
List以列表形式顺序存取元素,保证元素的插入顺序和存储顺序一致。
实现 | 线程安全 | |
---|---|---|
ArrayList | 数组 | 否 |
LinkedList | 双向链表 | 否 |
CopyOnWriteArrayList | 数组 | 是,使用CopyOnWrite保证线程安全 |
Vector | 数组 | 是,使用Synchronized保证线程安全 |
Stack | 数组 | 是,继承Vector |
Vector是Jdk1.0就引入的线程安全的列表实现,早于Collection接口设计,采用直接在方法上添加Synchronized来保证线程安全,Stack是继承Vector实现的栈结构,因为其线程安全的低效性,目前在实际环境均再也不推荐使用。
Queue是先进先出的结构,从队尾加入元素,队头弹出元素。其子接口Deque为双端队列,即两头均可以进出。
实现 | 线程安全 | |
---|---|---|
ArrayDeque | 循环数组 | 否 |
LinkedList | 链表 | 否 |
PriorityQueue | 堆 | 否 |
BlockingQueue | BlockingQueue为阻塞队列的扩展接口 | 是 |
因为BlockingQueue略为复杂,更涉及到一些进阶应用场景,留待后续讲解。
Set是不重复元素集合,元素中不包含相同元素,且一般状况不保持元素插入顺序
实现 | 线程安全 | |
---|---|---|
HashSet | 哈希表 | 否,基于HashMap实现 |
TreeSet | 红黑树 | 否,基于TreeMap实现 |
LinkedHashSet | 哈希表+链表 | 否,基于LinkedHashMap实现 |
CopyOnWriteArraySet | 数组 | 是,CopyOnWrite保证 |
ConcurrentSkipListSet | 跳表 | 是,基于ConcurrentSkipListMap实现 |
Map用于存储<Key, Value>键值对。
实现 | 线程安全 | |
---|---|---|
HashMap | 哈希表+红黑树 | 否 |
TreeMap | 红黑树 | 否 |
LinkedHashMap | 哈希表+链表+红黑树 | 否 |
WeakHashMap | 哈希表 | 否 |
ConcurrentHashMap | 哈希表+红黑树 | 是,基于节点CAS和Synchronized实现 |
ConcurrentSkipListMap | 跳表 | 是 |
Java在1.2版本引入JCF框架,Java范型是在1.5版本引入,所以在泛型引入以前集合默认以Object做为存储类型。
以List为例 List list = new ArrayList(); list.add(123); // 自动boxing list.add("123"); int num = (int) list.get(0); String str = (String) list.get(1);
显而易见该方式存在缺陷,集合内能够放入任何以Object为基类的元素,而元素的获取方没法肯定元素的具体类型,容易出现类型转换错误。在1.5版本引入范型之后,对集合接口进行规范,添加了范型参数,Java的泛型机制本质上仍是将具体类型擦除为Object,所以泛型集合在初始化时,没法将参数指定为非Object派生的基本类型。
List<String> list = new ArrayList(); list.add("123"); list.add("456"); //(1) throw ConcurrentModificationException for (String s : list) { list.remove(s); } //(2) 正常移除 Iterator<String> it = list.iterator(); while (it.hasNext()) { it.next(); it.remove(); } //(3) throw ConcurrentModificationException new Thread(() -> { for (String s : list) { System.out.println(s); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException ignore) { } } }).start(); new Thread(() -> {list.add("789");}).start();
上面这段代码,(1) (3) 会抛出ConcurrentModificationException,(2)能够正常移除全部元素。首先了解一下ConcurrentModificationException,当对一个对象作出的并发修改不被容许时,将抛出这个异常。
This exception may be thrown by methods that have detected concurrent modification of an object when such modification is not permissible.
那么(1)是单线程执行,(3)并发添加元素也并未对已有元素作修改,为何也会触发该异常呢。
ArrayList var1 = new ArrayList(); var1.add("123"); var1.add("456"); Iterator var2 = var1.iterator(); while(var2.hasNext()) { String var3 = (String)var2.next(); var1.remove(var3); }
对代码(1)的class文件反编译查看,发现foreach其实是经过Iterator作的迭代,迭代过程当中删除是直接调用list.remove。咱们再进入到list.iterator方法探个究竟。
/** Returns an iterator over the elements in this list in proper sequence. * The returned iterator is fail-fast. */ public Iterator<E> iterator() { return new Itr(); } private class Itr implements Iterator<E> { .... int expectedModCount = modCount; .... final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); } }
iterator方法会建立一个Itr对象,在其创时会复制modCount到expectedModCount,每进行迭代时都会判断两个值是否相同,若是不一样则抛出ConcurrentModificationException。
再来看modCount是一个继承自AbstractList的成员变量,用于记录list被修改的次数,每当调用add/remove时,modCount都会加1。
// The number of times this list has been structurally modified. protected transient int modCount = 0;
那么问题就很明显了,每当对list进行修改modCount都会改变,而foreach的iterator记录的是迭代对象建立时刻的modCount值,接下来的迭代过程当中,因为调用了list的修改方法,改变了其中modCount的值,致使modCount != expectedModCount,因而就抛出了异常。(3)代码是相同的问题,再也不进行赘述。
* <p><a name="fail-fast"> * The iterators returned by this class's {@link #iterator() iterator} and * {@link #listIterator(int) listIterator} methods are <em>fail-fast</em>:</a> * if the list is structurally modified at any time after the iterator is * created, in any way except through the iterator's own * {@link ListIterator#remove() remove} or * {@link ListIterator#add(Object) add} methods, the iterator will throw a * {@link ConcurrentModificationException}. Thus, in the face of * concurrent modification, the iterator fails quickly and cleanly, rather * than risking arbitrary, non-deterministic behavior at an undetermined * time in the future.
在全部Java集合类中,直接位于java.util下除Vector、Stack、HashTable外,全部的集合都是fail-fast的,而在java.util.concurrent下的集合都是fail-safe的,便可以并发的遍历和修改集合,具体实现由各自的线程安全机制保证。
为何须要fail-fast
fail-fast意为快速失败,在非线程安全的集合应用场景中,并发对集合作的添加/删除,可能致使另外一个正在遍历集合的线程出现未知的错误如数组越界。所以非线程安全的集合实现引入fail-fast以此来快速中断线程,避免引起未知的连锁问题。