Java多线程编程四 并发容器

并发容器小结

并发包中的 List--CopyOnWriteArrayList

并发包中的 List 只有 CopyOnWriteArrayList ,是一个线程安全的 ArrayList ,对其的修改都是在底层的一个复制数组(快照)上进行的,使用了写时复制的策略。这种实现只是保证数据的最终一致性,在添加到拷贝数据而还没进行替换的时候,读到的仍然是旧数据。如果对象比较大,频繁地进行替换会消耗内存,从而引发Java的GC问题,这个时候,我们应该考虑其他的容器,例如ConcurrentHashMap。

写时复制策略

核心思想是,如果有多个调用者(Callers)同时要求相同的资源(如内存或者是磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用副本(private copy)给该调用者,而其他调用者所见到的最初的资源仍然保持不变。这过程对其他的调用者都是透明的(transparently)。此做法主要的优点是如果调用者没有修改资源,就不会有副本(private copy)被创建,因此多个调用者只是读取操作时可以共享同一份资源。

使用场景

CopyOnWrite并发容器用于读多写少的并发场景。比如白名单,黑名单,商品类目的访问和更新场景,假如我们有一个搜索网站,用户在这个网站的搜索框中,输入关键字搜索内容,但是某些关键字不允许被搜索。这些不能被搜索的关键字会被放在一个黑名单当中,黑名单每天晚上更新一次。当用户搜索时,会检查当前关键字在不在黑名单当中,如果在,则提示不能搜索。

弱一致性

add() 方法添加元素,通过加独占锁 ReentrantLock 保证数据原子性操作,其中在添加元素的时候,首先复制了一个快照,然后在快照上进行添加,替换之前的数组,所以它是无界的。

remove() 方法也获取了独占锁,但是所有的读数据操作未加锁,产生了弱一致性问题--随着时间节点发展,结果逐渐一致。

CopyOnWriteArraySet 的底层是 CopyOnWriteArrayList 。

并发队列

并发队列,按照实现方式的不同可分为阻塞队列和非阻塞队列,前者使用锁实现,后者使用 CAS 非阻塞算法实现,线程安全。

ConcurrentLinkedQueue

内部的队列使用单向链表,有两个 volatile 的Node节点分别存放首位节点,通过 CAS 非阻塞算法保证出入队时操作链表的原子性。

//构造方法
public ConcurrentLinkedQueue(){
    head = tail = new Node(null);
}
//主要方法

//添加元素,二者一致,非阻塞方法,若传参为null,将报空指针异常
public boolean offer(E e);
public boolean add(E e);

//移除元素
public E poll();//在队列头部获取元素并将其移除,若队列为空则返回null;
public boolean remove(Object o);//移除元素

//查询队列元素
public E peek();//在队列头部获取元素,若队列为空则返回null;
public int size();//由于 CAS 没有加锁,在并发条件下不是很有用
public boolean contains(Object o);//查找该元素

LinkedBlockingQueue

有界链表实现有界阻塞队列,借助锁实现,线程安全,使用了 生产者-消费者 模式

//主要方法

public boolean offer(E e);//非阻塞添加元素,若队列已满则丢弃当前元素返回false,元素空指针检查
public boolean put(E e)throws InterruptedException;//阻塞添加元素,队列若满,则等待队列空闲后插入;在阻塞时被设置中断标志,会抛出异常而返回,元素参数空指针检查

//移除元素
public E poll();//在队列头部获取元素并将其移除,若队列为空则返回null;不阻塞
public boolean remove(Object o);//移除元素
public E take() throws InterruptedException;//阻塞获取并移除元素,在阻塞时被设置中断标志,会抛出异常而返回,元素参数空指针检查

//查询队列元素
public E peek();//在队列头部获取元素,若队列为空则返回null;不阻塞
public int size();//count变量被加锁,比较准确

ArrayBlockingQueue

有界数组实现有界阻塞队列;数组 items 存放队列元素,count统计元素个数,通过加独占锁实现出入队操作的原子性,条件变量 notEmpty,notFull 进行出入队的同步。

//主要方法

public boolean offer(E e);//非阻塞添加元素,若队列已满则丢弃当前元素返回false,元素空指针检查
public boolean put(E e)throws InterruptedException;//阻塞添加元素,队列若满,则等待队列空闲后插入;在阻塞时被设置中断标志,会抛出异常而返回,元素参数空指针检查

//获取或移除元素
public E poll();//在队列头部获取元素并将其移除,若队列为空则返回null;不阻塞
public E peek();//在队列头部获取元素,若队列为空则返回null;不阻塞
public E take() throws InterruptedException;//阻塞获取并移除元素,在阻塞时被设置中断标志,会抛出异常而返回,元素参数空指针检查

//查询队列元素
public int size();//count变量操作修改时被加全局锁,精确

PriorityBlockingQueue

带优先级的无界阻塞队列,每次出队都是优先级最高或最低的元素,内部是二叉平衡树堆,元素必须实现 Comparable 接口或 传入比较器。

//主要方法

public boolean offer(E e);//添加元素,元素空指针检查
public boolean put(E e);//添加元素,元素空指针检查

//获取或移除元素
public E poll();//获取根部元素并将其移除,若队列为空则返回null;不阻塞
public E take() throws InterruptedException;//队列为空则阻塞,阻塞获取并移除元素,在阻塞时被设置中断标志,会抛出异常而返回,元素参数空指针检查

//查询队列元素
public int size();//count变量操作修改时被加全局锁,精确

DelayQueue

无界阻塞延迟队列,队列中每个元素都有过期时间,只有过期元素才会出队,队列头元素为最快要过期元素,内部使用优先队列(堆)存放元素。

//主要方法

public boolean offer(E e);//添加元素,元素空指针检查

//获取或移除元素
public E poll();//获取头部过期元素并将其移除,若队列为空则返回null;不阻塞
public E take() throws InterruptedException;//队列没有过期元素则阻塞,阻塞获取并移除元素,在阻塞时被设置中断标志,会抛出异常而返回,元素参数空指针检查

//查询队列元素
public int size();//包含过期与未过期的元素

ConcurrentHashMap

和HashMap类似;

ConcurrentHashMap的底层实现为 数组+链表+红黑树(通过链地址法解决冲突);

使用CAS与synchronized实现并发安全;

不可以存放空键值

你可能感兴趣的:(Java多线程编程四 并发容器)