并发容器梳理
记录一个问题先:
AndroidS Studio打包APK时出现问题:org.gradle.api.tasks.TaskExecutionException: Execution failed for task ':app:lintVitalRelease'.
什么意思?任务':app:lintVitalRelease的执行失败。
关于lint是个什么东西看官网:https://developer.android.com/studio/write/lint?hl=zh-CN
或者:https://blog.csdn.net/u011240877/article/details/54141714#commentBox
总之,这是一个保证代码的可维护性与助力代码优化的一个工具,可以检查出代码当中不规范或者结构混乱等威胁代码质量的地方予以警告。
但是Lint的警告当中有的问题是不必要一定解决的,所以过度依赖这个工具,也可能造成代码编写时效率低下的问题。
所以在打包的过程当中,由于没有解决掉lint中警告的问题,没有办法通过编译进行打包。
所以可以在lint配置当中选择忽略它的警告,例如在app下的build.gradle下android闭包中添加:
lintOptions { checkReleaseBuilds false abortOnError false }
就能成功打包了。
ConcurrentHashMap、CopyOnWriteArrayList、阻塞队列
并发容器概览
-
ConcurrentHashMap:线程安全的HashMap
-
CopyOnWriteArrayList:线程安全的List
-
BlockingQueue:这是一个接口,表示阻塞队列,非常适合用于作为数据共享的通道。
-
ConcurrentLinkedQueue:高效的非阻塞并发队列,使用链表实现。可以看做是一个线程安全的LinkedList。
-
ConcurrentSkipListMap:是一个Map,使用跳表的数据结构进行快速查找。(不常用)
集合类的相关历史
-
Vector和Hashtable
这是早期JDK中线程安全的ArrayList和HashMap,并发性能差,所有方法都加上synchronized,那么意味着并发竞争大的时候性能不会太好。
-
ArrayList和HashMap
虽然这两个类不是线程安全的,但是可以用Collections.synchronizedList(new ArrayList<E>())和Collections.synchronizedMap(new HashMap<K,V>())使之变成安全的。这种方式也是根据传入的集合类的类型,比如是否RandomAccess类型,实现了RandomAccess接口,来返回一个对应的线程安全的SynchronizedRandomAccessList或者SynchronizedList,而在这些线程安全的list当中,使用synchronized方式并没有比Vector和Hashtable当中的方法高明到哪儿去,只是没有加在方法上而已。
-
ConcurrentHashMap、CopyOnWriteArrayList
这两种就到了比较不错的实现了,它们取代同步的HashMap和ArrayList。绝大多数并发情况下,ConcurrentHashMap和CopyOnWriteArrayList的性能都更好。除非是一个List经常被修改,那么用Collections.synchronizedList(new ArrayList<E>())会比使用CopyOnWriteArrayList性能更好,因为CopyOnWriteArrayList更适合读多写少的场景,每次写入都会完整复制整个链表,比较耗费资源。
ConcurrentHashMap
-
Map
Map是一个接口,有这些实现:
-
HashMap
-
Hashtable(性能太低,如果不需要并发就直接使用HashMap,而如果在并发场景下就使用ConcurrentHashMap)
-
LinkedHashMap:HashMap的一个子类,会保存键值对的插入顺序,在遍历的时候就有用了,顺序和插入顺序一致。
-
TreeMap:由于实现了SortedMap接口,所以也就具有排序的功能,也可以自定义排序的规则。所以遍历的时候也是排过序的。
常用方法:
int size();
boolean isEmpty();
boolean containsKey(Object key);
boolean containsValue(Object value);
V get(Object key);
V put(K key, V value);
V remove(Object key);
Set<K> keySet();
//等等 -
-
为什么需要ConcurrentHashMap
为什么不用Collections.synchronizedMap(new HashMap<K,V>())?
为什么HashMap是线程不安全的?
-
同时put碰撞导致数据丢失
-
同时put扩容导致数据丢失(扩容之后的数组只有一个会被保存下来)
-
死循环造成的CPU100%
这个问题主要是出现JDK1.7及之前存在。
核心原因就是在多线程同时扩容的时候会造成链表的死循环。但是这个问题吧,都说了HashMap不支持并发了,并发场景下使用自然会出现一些问题。
-
-
HashMap分析
-
JDK1.7拉链法
-
JDK1.8拉链法升级为红黑树
关于红黑树:红黑树是对二叉查找树BST的一种平衡策略,O(logN)vsO(N),会自动平衡,防止极端不平衡从而影响查找效率 的情况发生。红黑树的每个节点要么是红色,要么是黑色,但是根节点永远是黑色的;而且红色节点不能连续(也就是,红色节点的孩子和父亲都不能是红色);从任一节点到其子树中每个叶子节点的路径都有相同数量的黑色结点;所有叶结点都是黑色的。
-
HashMap关于并发的特点:
-
非线程安全
-
迭代的时候不允许修改内容
-
只读的并发是安全的
-
如果一定要把HashMap用在并发环境下,
用Collections.synchronizedMap(new HashMap<K,V>())。
-
-
-
JDK1.7中的ConcurrentHashMap的实现分析
Java7中的ConcurrentHashMap最外层是多个segment,每个segment的底层数据结构和HashMap类似,仍然是数组和链表组成的拉链法。每个segment独立上ReentrantLock锁,每个segment之间互不影响,提高了并发效率。ConcurrentHashMap默认有16个segment,所以最多可以同时支持16个线程并发写(操作分别分布在不同的segment上),这个默认值可以在初始化的时候设置为其他的值,但是一旦初始化以后,是不可以扩容的。
-
JDK1.8中的ConcurrentHashMap实现和分析
Java8中的ConcurrentHashMap是把代码完全的重写了,代码量也从一千多行涨到了六千多行。
putVal流程:
-
判断key不为空
-
计算hash值
-
根据对应位置的节点类型,来赋值,或者helpTransfer,或者增长链表,或者给红黑树增加节点。
-
检查满足阈值就“红黑树化”
-
返回oldVal。
get流程:
-
计算hash值
-
找到对应的位置,根据情况进行:
-
直接取值:
-
红黑树里取值
-
遍历链表取值
-
返回找到的结果
源码分析:
public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
implements ConcurrentMap<K,V>, Serializable {
private static final long serialVersionUID = 7249069246763182397L;
//省略。。。
//初始化,可见没有任何内容,真正的初始化要等到putval方法中调用initTable方法
public ConcurrentHashMap() {
}
/**
* Initializes table, using the size recorded in sizeCtl.
* 使用sizeCtl中记录的大小初始化表。
*/
//初始化 table,通过对 sizeCtl 的变量赋值来保证数组只能被初始化一次。
private final Node<K, V>[] initTable() {
Node<K, V>[] tab;
int sc;
//通过自旋保证初始化成功
while ((tab = table) == null || tab.length == 0) {
// 小于 0 代表有线程正在初始化,释放当前 CPU 的调度权,重新发起锁的竞争
if ((sc = sizeCtl) < 0)
Thread.yield(); // lost initialization race; just spin
// CAS 赋值保证当前只有一个线程在初始化,-1 代表当前只有一个线程能初始化
// 保证了数组的初始化的安全性
else if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try {
// 很有可能执行到这里的时候,table 已经不为空了,这里是双重 check
if ((tab = table) == null || tab.length == 0) {
// 进行初始化
int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
-