并发容器面面观

 

一,浅谈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,出队列返回一个节点元素,清空该节点引用。

 

 

 

 

 

posted @ 2021-08-08 17:25  牧码良匠  阅读(59)  评论(0编辑  收藏  举报