Java并发容器和框架总结
一、前言
Java并发容器和框架是我们在并发编程时的重要工具,本文是笔者对《java并发编程的艺术》一书中Java并发容器和框架相关的重点内容的总结和分析。
二、ConcurrentHashMap
在并发编程中使用HashMap可能会导致程序死循环。而使用线程安全的HasnTable效率又非常低下,基于这两个原因,java提供了ConcurrentHashMap
其中HashMap多线程可能会导致的问题如下:
1.多线程put操作会导致数据丢失。
2.多线程put操作后,get操作导致死循环(1.8以前,主要是扩容方法resize()中调用transfer()方法转移元素形成循环链表导致)。
HashTable的效率低下问题:
HashTable容器是使用synchronized来保证线程安全,但在线程竞争激烈的情况下HashTable的效率非常低下。
ConcurrentHashMap在JDK8做了优化,以前是通过分段锁来实现,JDK8摒弃了Segment的概念,使用CAS+Synchronzied来实现。
三、ConcurrentLinkedQueue
在并发编程中,如果要实现一个线程安全的队列有两种方式:一种是使用阻塞算法,另一种是使用非阻塞算法。使用阻塞算法的队列可以用一个锁(入队出队用同一把锁)或两个锁(入队和出队用不同的锁)等方式来实现。非阻塞的方式可以使用循环CAS的方式来实现。
ConcurrentLinkedQueue是一个基于链接节点的*线程安全队列,它采用先进先出的规则对节点进行排序,当我们添加一个元素的时候,它会添加到队列的尾部;当我们获取一个元素的时候,它会返回队列头部的元素。
入队:
从源码角度来看(优化了JDK7代码),入队操作干了两件事:第一件事就是定位出尾节点;第二是在两次或者以上入队操作更新tail节点(所以tail节点不一定是尾元素)。
出队:
四、Java中的阻塞队列
阻塞队列(BlockingQueue)是一个支持两个附加操作的队列。这两个附加的操作支持阻塞的插入和移除方法。
(1)支持阻塞的插入方法:意思是当队列满时,队列会阻塞插入元素的线程,直到队列不满。
(2)支持阻塞的移除方法:意思在队列为空时,获取元素的线程会等待队列变为非空。
在阻塞队列不可用时,这两个附加操作提供了4种处理方式:
注意:如果是*阻塞队列,队列不可能会出现满的情况,所以使用put或offer方法永远不会被阻塞,而且使用offer的方法时,该方法永远返回true。
JDK 8提供了7个阻塞队列,如下:
(1)ArrayBlockingQueue: 一个由数组结构组成的有界阻塞队列。此队列按照先进先出(FIFO)的原则对元素进行排序(默认是非公平的访问队列,可以构造公平的访问队列,但通常会降低吞吐量)。
(2)LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列。此队列的默认和最大长度为Integer.MAX_VALUE,此队列按照先进先出的原则对元素进行排序。
(3)PriorityBlockingQueue是一个支持优先级的*阻塞队列。默认情况下元素采取自然顺序升序排列。也可以自定义类实现compareTo()方法来指定元素的排序规则,或者初始化PriorityBlockingQueue时,指定构造参数Comparator来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。
(4)DelayQueue是一个支持延时获取元素的*阻塞队列。队列使用PriorityBlockingQueue来实现。队列中的元素必须实现Delayed接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。
(5)SynchronousQueue
SynchronousQueue是一个不存储元素的阻塞队列。每一个put操作必须等待一个take操作,否则不能继续添加元素(它支持公平访问队列,默认情况下)。
(6)LinkedTransferQueue
LinkedTransferQueue是一个由链表结构组成的*阻塞TrtansferQueue队列。相对于其他的阻塞队列,LinkedTransferQueue对了tryTransfer和transfer方法。如果当前有消费者正在等待接受元素(消费者使用take方法或者带时间限制的poll方法时,transfer方法可以把生产者传入的元素立刻transfer(传输)给消费者。如果没有消费者在等待接受元素,transfer方法会将元素存放在队列的tail节点,并等到该元素被消费者消费了才返回。tryTransfer方法是用来试探生产者传入的元素能否直接传给消费者。如果没有消费者等待接受元素,则返回false,和transfer方法的区别是tryTransfer方法无论消费者是否接受元素,方法都立刻返回,而transfer方法必须是等待消费者消费了才返回。
(7)LinkedBlockingDeque
LinkedBlockingDeque是一个由链表结构组成的双向阻塞队列。
五、Fork/join框架
Fork/Join框架主要有两个目的:1.分割任务。2.执行任务并合并结果。
Fork/Join使用两个类来完成以上两件事情。
(1)ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务,它提供在任务中执行fork()和join()操作的机制。通常情况下,我们不需要直接继承ForkJoinTask类,只需要继承它的自雷,Fork/Join框架提供了以下两个子类。RecursiveAction用于没有返回结果的任务。RecursiveTask用户有返回结果的任务。
(2)ForkJoinPool:ForkJoinTask需要通过ForkJoinPool来执行。
任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务。
个人博客地址:http://xuyangyang.club(点击打开)
微信订阅号: