1 介绍
在Java中有没有一种队列的实现方式可以不用关心临界值的判断,操作该队列的线程也不会被挂起并且等待被其他线程唤醒,我们只是单纯地向该队列中插入或者获取数据,并且该队列是线程安全的,是可以应用于高并发多线程的场景中呢?在JDK1.5版本以前要实现这些要求我们大致有两种方式,具体如下:
- 通过synchronized关键字对非线程安全的队列或者链表的操作方法进行同步。
- 使用Collections类的同步方法
但是synchronized关键字相对于显式锁Lock甚至无锁的实现方式来说效率低下,因此自JDK1.5版本后,Java的开发者们实现了无锁的且线程安全的并发队列实现方案,开发者可以直接借助于它们开发出高性能的应用程序
- ConcurrentLinkedQueue:无锁的、线程安全的、性能高效的、基于链表结构实现的FIFO单向队列(在JDK1.5版本中被引入)
- ConcurrentLinkedDeque:无锁的、线程安全的、性能高效的、基于链表结构实现的双向队列(在JDK1.7版本中被引入)。
ConcurrentLinkedQueue和ConcurrentLinkedDeque的使用都是比较简单的,就不做过多介绍
2 并发队列在使用中需要注意的问题
-
在并发队列中使用size方法不是个好主意
我们知道每一个Collection(队列Queue也是Collection的子接口)都提供了size()方法用于获取Collection中的元素个数,但是在并发队列中执行该方法却不是一个明智的操作:- 首先,并发队列是基于链表的结构实现的,并且在其内部并未提供类似于计数器的变量(当元素插入队列计数器时增一,当元素从队列头部被移除时计数器减一),因此想要获得当前队列的元素个数,需要遍历整个队列才能计算得出(效率低下)。
- 其次并发队列采用无锁(Lock-Free)的算法实现,因此在某个线程中执行size()方法获取元素数量的同时,其他线程也可以对该队列进行读写操作,所以size()返回的数值不会是一个精确值,而是一个近似值、一个估计值。
-
ConcurrentLinkedQueue的内存泄漏问题
ConcurrentLinkedQueue在执行remove方法删除元素时还会出现性能越来越低,甚至内存泄漏的问题。这个问题最早是由Jetty的开发者发现的,因为Jetty内部的线程池采用的就是ConcurrentLinkedQueue作为任务的队列,随后在很多开源项目中都发现了内存泄漏的问题