并发容器面面观
一,浅谈ReentrantLock
A, Lock接口及ReentrantLock简介
ReentrantLock实现的接口为Lock,Lock接口是锁操作方法的基本定义,提供了synchronized关键字所具备的全部功能。
与synchronized同步代码块的方式不同,Lock接口提供了编程式的锁获取及释放操作,由程序员自主控制。
Lock接口及子类图:
B,ReentrantLock类 vs synchronized关键字
synchronized的使用比较方便,不需要开发者手动加锁和释放锁。
ReentrantLock需要手工声明来加锁和释放锁(lock() 和 unlock() 方法配合 try/finally 语句块来实现)
ReentrantLock 在锁的细粒度和灵活度上优于synchronized。还增加了一些高级特性,主要有以下3项:等待可中断、可实现公平锁以及锁可以绑定多个条件。
以下为性能对比:
1,单线程读操作性能对比 ,synchronized 胜
2,多线程读操作性能对比, ReentrantLock胜
3,多线程下读写操作性能对比,ReentrantLock胜
C,synchronized同ReentrantLock使用取舍
在JDK 1.6之后,虚拟机对于synchronized关键字进行整体优化后,在性能上synchronized与ReentrantLock已没有明显差距,JDK1.9中,更是基本持平。
大部分情况下我们依然建议是synchronized关键字,原因之一是使用方便语义清晰,二是性能上虚拟机已为我们自动优化。
而ReentrantLock提供了多样化的同步特性,如超时获取锁、可以被中断获取锁(synchronized的同步是不能中断的)、等待唤醒机制的多个条件变量(Condition)等,因此当我们确实需要使用到这些功能是,可以选择ReentrantLock。
二,ConcurrentHashMap篇
A,为什么线程安全且高效?
1,多线程环境下使用HashMap做put操作时,导致HashMap的Entry链表形成环形数据结构,造成其next节点永远不为空,产生死循环。并发环境禁止使用HashMap。
2,使用syncchronized的HashTable保证线程安全的同时,在多线程环境下会进入进入阻塞或轮询状态,竞争越激烈效率越低。
3,同HashTable不同,ConcurrentHashMap使用的锁分段技术,将数据按段存储,每段分配一把锁。多线程访问时,不存在锁竞争进而提高并发效率。
B,数据结构
核心有Segment数组结构和HashEntry数据结构构成,Segment是ReentrantLock可重入锁,HashEntry用于存储键值对,存在于Segment中。
1,类图如下:
2,结构图如下:
C,初始化
主要是初始化segment数组、段偏移量segmentShift、段掩码segmentMask及HashEntry数组
concurrentcyLeve(Max=65536)l计算出segments数组的长度(2的N次方,Max=65536)
segmentShift用于定位参与散列运算的位数
segmentMask是散列运算的掩码
initialCapacity是初始化容量,loadfactor是每个segment的负载因子,这两参数用来初始化数组中的segment
1,初始化segments数组,如下图源代码
2,初始化每个segment,如下图:
3,定位Segment,使用下图散列算法定位
D,相关数据操作
1,get操作高效之处在于其不需要加锁,除非空值加锁;Why? 共享变量定义为volatile类型,保持可见性。这里使用Java内存模型的happen before原则,对volatile字段的写入操作先于读操作,保证数据为最新的。
2,put动作,在操作共享变量时务必加锁,首先定位Segment,而后进行插入操作。
3,size操作,尝试2次不锁住Segment方式来统计各个Segment大小,统计过程中,发现容器的count发生了变化(使用modCount变量同size对比,前者在写操作会更新加1),在采用加锁的方式来统计Segment的大小。
三,CopyOnWriteArrayList篇
A,为甚么使用CopyOnWriteArrayList?
1,ArrayList性能问题:非线程安全,并发场景下可能导致add元素为null,并发修改list内容抛ConcurrentModificationException异常等问题
2,Vector是线程安全的列表(JDK1.0开始存在),但线程安全实现简单粗暴,只是加了synchronized关键字,严重影响效率。
3,Collections.syncchronizedList,生成了同步的SynchronizedCollection,对于迭代操作并没有提供锁机制,需要手动加锁。
4,CopyOnWriteArrayLis(JDK1.5引入)t优点:读写分离,数据一致性。
5,CopyOnWriteArraySet,高并发的Set解决方案,基于CopyOnWriteArrayList实现。
B,核心元素
1,数组
2,独占锁,ReentrantLock
3,迭代器
C,读写操作分析
读:
写:
无界List,使用写时复制的策略来保证list的一致性
获取-修改-写入,三步操作并不是原子性的,增删改的过程使用了独占锁。
提供了弱一致性的迭代器,保证在获取迭代器后,其他线程对list的修改时不可见。
D,使用场景
缺点:数组复制带来的内存开销;无法保证数据一致性
应用:读操作远大于写操作,不要求数据实时一致性。如,规则引擎使用、黑白名单使用等。
四,ConcurrentLinkedQueue篇
A,实现线程安全队列的两种方式:
1,阻塞算法-LinkedBlockingQueue ,多用于任务队列
2,非阻塞算法–ConcurrentLinkedQueue,多用于消息队列
ConcurrentLinkedQueue基于链接节点的无界线程安全队列,先进先出的规则对节点进行排序。其采用了CAS算法实现(wait-free)。
B,类图
主要有head节点和tail节点,每个节点有元素和next节点构成。
head及tail使用volatile修饰,且不可序列化。
C,入队列&出队列
1,入队方法永远返回true,不用通过返回值判断
2,出队列返回一个节点元素,清空该节点引用。