并发编程栏目代码 GitHub package 地址: 点击打开链接
博客并发编程栏目 : 点击打开链接
在Java多线程应用中,队列的使用率很高,多数生产消费模型的首选数据结构就是队列。
Java提供的线程安全的Queue可以分为阻塞队列和非阻塞队列,其中阻塞队列的典型例子是BlockingQueue,非阻塞队列的典型例子是ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列。
注:什么叫线程安全?这个首先要明确。线程安全就是说多线程访问同一代码,不会产生不确定的结果。
LinkedBlockingQueue
由于LinkedBlockingQueue实现是线程安全的,实现了先进先出等特性,是作为生产者消费者的首选,LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。详细方法使用参考:LinkedBlockingQueue 实现生产者消费者模型
ConcurrentLinkedQueue
ConcurrentLinkedQueue是Queue的一个安全实现.Queue中元素按FIFO原则进行排序.采用CAS操作,来保证元素的一致性。 对比锁机制的实现,使用无锁机制的难点在于要充分考虑线程间的协调。简单的说就是多个线程对内部数据结构进行访问时,如果其中一个线程执行的中途因为一些原因出现故障,其他的线程能够检测并帮助完成剩下的操作。这就需要把对数据结构的操作过程精细的划分成多个状态或阶段,考虑每个阶段或状态多线程访问会出现的情况。
ConcurrentLinkedQueue有两个volatile的线程共享变量:head,tail。要保证这个队列的线程安全就是保证对这两个Node的引用的访问(更新,查看)的原子性和可见性,由于volatile本身能够保证可见性,所以就是对其修改的原子性要被保证。
另外还说一下,ConcurrentLinkedQueue的size()是要遍历一遍集合的,所以尽量要避免用size而改用isEmpty(),以免性能过慢。
/*
* 方法摘要
* boolean add(E e)
* 将指定元素插入此队列的尾部。
*
* boolean contains(Object o)
* 如果此队列包含指定元素,则返回 true。
*
* boolean isEmpty()
* 如果此队列不包含任何元素,则返回 true。
*
* Iterator<E> iterator()
* 返回在此队列元素上以恰当顺序进行迭代的迭代器。
*
* boolean offer(E e)
* 将指定元素插入此队列的尾部。
*
* E peek()
* 获取但不移除此队列的头;如果此队列为空,则返回 null。
*
* E poll()
* 获取并移除此队列的头,如果此队列为空,则返回 null。
*
* boolean remove(Object o)
* 从队列中移除指定元素的单个实例(如果存在)。
*
* int size()
* 返回此队列中的元素数量。
*
* Object[] toArray()
* 返回以恰当顺序包含此队列所有元素的数组。
*
* <T> T[]
* toArray(T[] a)
* 返回以恰当顺序包含此队列所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。
*/
总结
阻塞算法其实很好理解,简单点理解就是加锁,比如在BlockingQueue中看到的那样,再往前推点,那就是synchronized。相比而言,非阻塞算法的设计和实现都很困难,要通过低级的原子性来支持并发。下面就简要的介绍一下非阻塞算法,以下部分的内容参照了一篇很经典的文章http://www.ibm.com/developerworks/cn/java/j-jtp04186/
注:我觉得可以这样理解,阻塞对应同步,非阻塞对应并发。也可以说:同步是阻塞模式,异步是非阻塞模式 ??吗??
其实,最终了解的应该是阻塞和非阻塞的区别,运用阻塞队列还是非阻塞队列去完成需求,以及两者的底层实现方式。
本文对队列的入门以及使用时候注意事项进行测试说明。
对于阻塞等待与轮询判断,区别到底在哪儿,性能上呢。
研究以后上链接。
测试代码 UML
两者的性能测试
/**
* @author wei.Li by 14-8-28.
*/
public class QueueCompare {
private static final org.slf4j.Logger LOGGER
= LoggerFactory.getLogger(QueueCompare.class);
/**
* 生产者、消费者
*/
interface Market<V> {
void producer(V v);
void consumer();
}
/**
* concurrentLinkedQueue 的生产与消费实现
*/
private static class ConcurrentLinkedQueueMarket<V> implements Market<V> {
@Override
public void producer(V o) {
concurrentLinkedQueue.add(o);
// LOGGER.info("concurrentLinkedQueue <{}> producer <{}>", concurrentLinkedQueue, o);
}
@Override
public void consumer() {
while (!concurrentLinkedQueue.isEmpty()) {//return first() == null; !!! size 方法是遍历队列返回总数
concurrentLinkedQueue.poll();
// LOGGER.info("concurrentLinkedQueue <{}> consumer <{}>", linkedBlockingQueue, o);
}
}
}
/**
* linkedBlockingQueue 的生产与消费实现
*/
private static class LinkedBlockingQueueMarket<V> implements Market<V> {
@Override
public void producer(V o) {
try {
linkedBlockingQueue.put(o);
//LOGGER.info("linkedBlockingQueue <{}> producer <{}>", linkedBlockingQueue, o);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void consumer() {
while (!linkedBlockingQueue.isEmpty()) {//return size() == 0; 与直接使用 size 方法无区别
try {
linkedBlockingQueue.take();
// LOGGER.info("linkedBlockingQueue <{}> consumer <{}>", linkedBlockingQueue, o);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 生产处理线程
*
* @param <T> extends Market
*/
private static class ProducerHandle<T extends Market<V>, V> implements Runnable {
T market;
V v;
private ProducerHandle(T market, V v) {
this.market = market;
this.v = v;
}
@Override
public void run() {
for (int i = 0; i < PRODUCER_OBJ_NUM; i++) {
market.producer(v);
}
}
}
/**
* 消费处理线程
*
* @param <T> extends Market
*/
private static class ConsumerHandle<T extends Market<V>, V> implements Runnable {
T market;
V v;
private ConsumerHandle(T market, V v) {
this.market = market;
this.v = v;
}
@Override
public void run() {
market.consumer();
LOGGER.info(" <{}> done <{}> need time <{}>"
, market.getClass().getSimpleName()
, PRODUCER_OBJ_NUM
, DateTime.now().toString(ISODateTimeFormat.dateHourMinuteSecond()));
}
}
//执行的线程数量
private static final int SYNCHRONIZED_DONE_THREAD_NUM = 4;
//线程池
private static final ExecutorService EXECUTOR_SERVICE
= Executors.newFixedThreadPool(SYNCHRONIZED_DONE_THREAD_NUM);
//linkedBlockingQueue init
private static LinkedBlockingQueue linkedBlockingQueue
= new LinkedBlockingQueue();
//concurrentLinkedQueue init
private static ConcurrentLinkedQueue concurrentLinkedQueue
= new ConcurrentLinkedQueue();
//测试生产数量
public static final int PRODUCER_OBJ_NUM = 10000000;
private static void runTest() {
/**
* 添加concurrentLinkedQueue生产线程
*/
Market<String> concurrentLinkedQueueMarket =
new ConcurrentLinkedQueueMarket<>();
EXECUTOR_SERVICE.execute(
new ProducerHandle<>(concurrentLinkedQueueMarket, "concurrentLinkedQueueMarket")
);
EXECUTOR_SERVICE.execute(
new ConsumerHandle<>(concurrentLinkedQueueMarket, "concurrentLinkedQueueMarket")
);
/**
* 添加blockingQueue生产线程
*/
Market<String> blockingQueueMarket
= new LinkedBlockingQueueMarket<>();
EXECUTOR_SERVICE.execute(
new ProducerHandle<>(blockingQueueMarket, "blockingQueueMarket")
);
EXECUTOR_SERVICE.execute(
new ConsumerHandle<>(blockingQueueMarket, "blockingQueueMarket")
);
EXECUTOR_SERVICE.shutdown();
}
public static void main(String[] args) {
runTest();
}
}
结果分析
@see com.thread.concurrent_.queue.concurrentlinkedqueue.example.QueueCompare.ConcurrentLinkedQueueMarket#consumer()
--------------------------------------------------------------------------
<LinkedBlockingQueueMarket> done <10000> need time <2014-08-29T09:46:21>
<ConcurrentLinkedQueueMarket> done <10000> need time <2014-08-29T09:46:21>
<LinkedBlockingQueueMarket> done <100000> need time <2014-08-29T09:46:46>
<ConcurrentLinkedQueueMarket> done <100000> need time <2014-08-29T09:47:12>
<LinkedBlockingQueueMarket> done <1000000> need time <2014-08-29T09:47:33>
<ConcurrentLinkedQueueMarket> done <1000000> need time <2014-08-29T.超长耗时>
ConcurrentLinkedQueueMarket采用 isEmpty() 方法判断大小
--------------------------------------------------------------------------
<LinkedBlockingQueueMarket> done <1000000> need time <2014-08-29T09:53:24>
<ConcurrentLinkedQueueMarket> done <1000000> need time <2014-08-29T09:53:24>
<ConcurrentLinkedQueueMarket> done <10000000> need time <2014-08-29T09:55:54>
<LinkedBlockingQueueMarket> done <10000000> need time <2014-08-29T09:55:54>
ConcurrentLinkedQueue的size()是要遍历一遍集合的,所以尽量要避免用size而改用isEmpty(),以免性能过慢。