集合
-
谈谈对集合的理解
集合是java.util包中的,是一种数据存储容器
分为Collection接口(value)和Map接口(key-value)
Collection:
List接口:有序可重复的
Set接口:无序不可重复的
Map接口:无序不可重复的
List接口:
ArrayList: 有序可重复----实现RandomAccess接口,实现下标查询
底层使用Object数组,初始化容量为10,扩容到1.5倍。
特点:基于数组,查询效率快,插入删除效率慢
LinkedList:基于双向链表;有序;查找速度慢,添加和删除效率快
Vector:线程安全,有大量的synchronized线程安全的方法;
底层是object数组,初始容量为10,扩容到2倍
Set接口:
HashSet:底层使用HashMap,以key为主,value为空对象
LinkedHashSet:底层使用LinkedHashMap,有序
TreeHashSet:底层使用TreeMap,可自动排序,实现Compareator,Compareable接口
Map接口:key-value键值对为主,无序不可重复
HashMap: key-value可以为空;底层使用数据+链表+红黑树,提高搜索效率
无参为16,有参则根据原有容量*0.75来扩容到原来的二倍
数组长度大于64,链表长度大于8则转为红黑树
LinkedHashMap: 底层是单向链表;继承于HashMap;有序
TreeMap:底层红黑树;可以自动排序;实现Compareable(compareTo()方法)和Compareactor(compare()方法)接口
HashTable:key,value不允许为空,为空发生空指针异常
线程安全;底层数组+链表,无参:11 有参扩容到2倍+1
-
集合中哪些线程是安全的
CopyOnWirteArrayList:
Java.util. concurrent并发包下的线程安全的List集合,兼顾安全和性能
在线程安全的情况下代替vector和ArrayList.进行增删改操作时,通过ReentrantLock加锁,复制出来一个新数组的方式来实现线程安全,用于读多写少的情况。
对原有的数据进行一次复制,将修改的内容写入新副本,写完后,再将新数据代替原有数据,保证写操作不会影响读操作。只能保证数据的最终一致性,但不保证数据实时一致性。增删改为新数组,读写为原数组。
缺点:由于每次都要创建一个新数组,消耗内存。
只能保证数据最终一致性,不能保证数据实时性。
ConcurrentHashMap:
基于线程安全的HashMap,底层采用分段的数组+链表+红黑树;将数据分为一段一段的存储,给每一段数据分配一把锁,当一个线程占用这个数据段的锁时,其他段也可以被其他线程访问。
Jdk1.7: 一个ConcurrentHashMap里包含一个Segment数组。Segment的结构和HashMap类似,是一种数组和链表结构,一个segment包含一个HashEntity数组,每个Entity是一个链表结构的元素,一个segment守护一个Entity数组的全部元素
Jdk1.8:取消了segment分段锁,采用CAS和synchronized来保证并发安全。Synchronized只锁定当前链表或红黑二叉树的首节点,提高了效率。
-
HashMap在多线程环境中存在线程安全问题,一般如何处理
- 使用Collections.synchronizedMap(Map)创建线程安全的map集合
- 使用Hashtable
- 使用ConcurrentHashMap
SynchronizedMap内部维护了一个普通的Map对象,还有排斥锁mutex,若构造方法中没有传入mutex,则排他锁为当前对象,若构造方法有则采用传入的对象做锁
Hashtable内部的方法全部采用Synchronized进行加锁,封锁粒度过大且为重量级锁。
ConcurrentHashMap:
Jdk1.7:数组+链表;采用sgement分段锁进行处理,sgement数组中包含了hashEntry数组,保证一个线程访问的同时,其他线程可以访问其他的分段数据。此外,为value添加了volatile(可见性,禁止指令重排,无原子性),当进行put操作时,先通过sagment中的(table-1)&hash(key)>>>低16位得到index,先尝试获取锁,若获取不到则代表其他线程在竞争,从而进行自旋,若自旋失败进行锁升级,从而进行赋值。其get操作就是通过key和hash获取指定位置进行拿值即可。
Jdk1.8 数组+链表+红黑树,采用synchronized+CAS保证线程安全性。底层可是给value加volatile保证其他线程拿到的value是最新的。在put操作时,通过一系列的运算获取index下标,若下标为null,则代表可以进行添加,先通过CAS尝试写入,若写入失败进行自旋,若自旋次数超过指定此时后,通过synchronized锁进行写入,其他线程等待。若不为空,则判断key是否一致,若一致则覆盖,若不一致,则进行向链表尾部添加数据。(synchronized只在链表首部和红黑树首部进行加锁)
4. fail-fast和fail-safe的理解
fail-fast(快速失败):java集合中的一种机制,适用于hashmap,在迭代器遍历一个集合对象时,如果遍历过程中对象的内容进行了修改(添加,修改,删除),从而抛出ConcurrentModification Exception.
原理:迭代器遍历时直接访问集合中的元素,并在遍历过程中使用一个modcount变量。
集合在遍历期间若内容发生变化,就改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,先检查modCount是否为expectedmodCount值,是的话返回遍历,否则抛出异常,终止遍历。
由于集合元素发生改变时,若将modCount设置为expectedmodCount,则不抛出异常,因此,不能依赖是否抛出异常而进行并发操作,该异常仅用于检测并发修改的bug
Fail-safe(安全失败):java.util.concurrent包下的容器都是安全失败,可以在多线程下并发使用,并发修改。
原理:由于迭代时是对原有集合的拷贝进行遍历,所以在遍历过程中对原有集合所作的修改并不能被迭代器检测到,从而不引发fail-fast.
缺点:在迭代器遍历拷贝集合时,无法知道原集合的修改