并发队列ConcurrentLinkedQueue与阻塞队列LinkedBlockingQueue的区别

并发编程栏目代码 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();
    }


}

结果分析

ConcurrentLinkedQueueMarket采用 size() 方法判断大小(每次遍历队列返回总数)
 @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(),以免性能过慢。

转载于:https://my.oschina.net/huluerwa/blog/311316

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值