https://blog.csdn.net/heqifan19891115/article/details/72302990
https://blog.csdn.net/jameshadoop/article/details/52729796
本文转自:并发队列ConcurrentLinkedQueue和阻塞队列LinkedBlockingQueue用法
在Java多线程应用中,队列的使用率很高,多数生产消费模型的首选数据结构就是队列(先进先出)。Java提供的线程安全的Queue可以分为阻塞队列和非阻塞队列,其中阻塞队列的典型例子是BlockingQueue,非阻塞队列的典型例子是ConcurrentLinkedQueue,在实际应用中要根据实际需要选用阻塞队列或者非阻塞队列。
注:什么叫线程安全?这个首先要明确。线程安全就是说多线程访问同一代码,不会产生不确定的结果。
并行和并发区别
1、并行是指两者同时执行一件事,比如赛跑,两个人都在不停的往前跑;
2、并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率
LinkedBlockingQueue
由于LinkedBlockingQueue实现是线程安全的,实现了先进先出等特性,是作为生产者消费者的首选,LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。
-
package cn.thread;
-
import java.util.concurrent.BlockingQueue;
-
import java.util.concurrent.ExecutorService;
-
import java.util.concurrent.Executors;
-
import java.util.concurrent.LinkedBlockingQueue;
-
/**
-
* 多线程模拟实现生产者/消费者模型
-
*
-
* @author 林计钦
-
* @version 1.0 2013-7-25 下午05:23:11
-
*/
-
public class BlockingQueueTest2 {
-
/**
-
*
-
* 定义装苹果的篮子
-
*
-
*/
-
public class Basket {
-
// 篮子,能够容纳3个苹果
-
BlockingQueue<String> basket = new LinkedBlockingQueue<String>(3);
-
// 生产苹果,放入篮子
-
public void produce() throws InterruptedException {
-
// put方法放入一个苹果,若basket满了,等到basket有位置
-
basket.put("An apple");
-
}
-
// 消费苹果,从篮子中取走
-
public String consume() throws InterruptedException {
-
// take方法取出一个苹果,若basket为空,等到basket有苹果为止(获取并移除此队列的头部)
-
return basket.take();
-
}
-
}
-
// 定义苹果生产者
-
class Producer implements Runnable {
-
private String instance;
-
private Basket basket;
-
public Producer(String instance, Basket basket) {
-
this.instance = instance;
-
this.basket = basket;
-
}
-
public void run() {
-
try {
-
while (true) {
-
// 生产苹果
-
System.out.println("生产者准备生产苹果:" + instance);
-
basket.produce();
-
System.out.println("!生产者生产苹果完毕:" + instance);
-
// 休眠300ms
-
Thread.sleep(300);
-
}
-
} catch (InterruptedException ex) {
-
System.out.println("Producer Interrupted");
-
}
-
}
-
}
-
// 定义苹果消费者
-
class Consumer implements Runnable {
-
private String instance;
-
private Basket basket;
-
public Consumer(String instance, Basket basket) {
-
this.instance = instance;
-
this.basket = basket;
-
}
-
public void run() {
-
try {
-
while (true) {
-
// 消费苹果
-
System.out.println("消费者准备消费苹果:" + instance);
-
System.out.println(basket.consume());
-
System.out.println("!消费者消费苹果完毕:" + instance);
-
// 休眠1000ms
-
Thread.sleep(1000);
-
}
-
} catch (InterruptedException ex) {
-
System.out.println("Consumer Interrupted");
-
}
-
}
-
}
-
public static void main(String[] args) {
-
BlockingQueueTest2 test = new BlockingQueueTest2();
-
// 建立一个装苹果的篮子
-
Basket basket = test.new Basket();
-
ExecutorService service = Executors.newCachedThreadPool();
-
Producer producer = test.new Producer("生产者001", basket);
-
Producer producer2 = test.new Producer("生产者002", basket);
-
Consumer consumer = test.new Consumer("消费者001", basket);
-
service.submit(producer);
-
service.submit(producer2);
-
service.submit(consumer);
-
// 程序运行5s后,所有任务停止
-
// try {
-
// Thread.sleep(1000 * 5);
-
// } catch (InterruptedException e) {
-
// e.printStackTrace();
-
// }
-
// service.shutdownNow();
-
}
-
}
ConcurrentLinkedQueue
ConcurrentLinkedQueue是Queue的一个安全实现.Queue中元素按FIFO原则进行排序.采用CAS操作,来保证元素的一致性。
LinkedBlockingQueue是一个线程安全的阻塞队列,它实现了BlockingQueue接口,BlockingQueue接口继承自java.util.Queue接口,并在这个接口的基础上增加了take和put方法,这两个方法正是队列操作的阻塞版本。
-
package cn.thread;
-
import java.util.concurrent.ConcurrentLinkedQueue;
-
import java.util.concurrent.CountDownLatch;
-
import java.util.concurrent.ExecutorService;
-
import java.util.concurrent.Executors;
-
public class ConcurrentLinkedQueueTest {
-
private static ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<Integer>();
-
private static int count = 2; // 线程个数
-
//CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
-
private static CountDownLatch latch = new CountDownLatch(count);
-
public static void main(String[] args) throws InterruptedException {
-
long timeStart = System.currentTimeMillis();
-
ExecutorService es = Executors.newFixedThreadPool(4);
-
ConcurrentLinkedQueueTest.offer();
-
for (int i = 0; i < count; i++) {
-
es.submit(new Poll());
-
}
-
latch.await(); //使得主线程(main)阻塞直到latch.countDown()为零才继续执行
-
System.out.println("cost time " + (System.currentTimeMillis() - timeStart) + "ms");
-
es.shutdown();
-
}
-
/**
-
* 生产
-
*/
-
public static void offer() {
-
for (int i = 0; i < 100000; i++) {
-
queue.offer(i);
-
}
-
}
-
/**
-
* 消费
-
*
-
* @author 林计钦
-
* @version 1.0 2013-7-25 下午05:32:56
-
*/
-
static class Poll implements Runnable {
-
public void run() {
-
// while (queue.size()>0) {
-
while (!queue.isEmpty()) {
-
System.out.println(queue.poll());
-
}
-
latch.countDown();
-
}
-
}
-
}
运行结果:
costtime 2360ms
改用while (queue.size()>0)后
运行结果:
cost time 46422ms
结果居然相差那么大,看了下ConcurrentLinkedQueue的API原来.size()是要遍历一遍集合的,难怪那么慢,所以尽量要避免用size而改用isEmpty().
关于ConcurrentLinkedQueue一个不错的例子:
http://blog.csdn.net/rsun1/article/details/8186694
ConcurrentLinkedQueue、AraayBlockingQueue、LinkedBlockingQueue 区别及使用场景
三者区别与联系:
联系,三者 都是线程安全的。区别,就是 并发 和 阻塞,前者为并发队列,因为采用cas算法,所以能够高并发的处理;后2者采用锁机制,所以是阻塞的。注意点就是前者由于采用cas算法,虽然能高并发,但cas的特点造成操作的危险性,怎么危险性可以去查一下cas算法(但一些多消费性的队列还是用的它,原因看下边使用场景中的说明)
后2者区别:
联系,第2和第3都是阻塞队列,都是采用锁,都有阻塞容器Condition,通过Condition阻塞容量为空时的取操作和容量满时的写操作第。区别,第2就一个整锁,第3是2个锁,所以第2第3的锁机制不一样,第3比第2吞吐量 大,并发性能也比第2高。
后2者的具体信息: LinkedBlockingQueue是BlockingQueue的一种使用Link List的实现,它对头和尾(取和添加操作)采用两把不同的锁,相对于ArrayBlockingQueue提高了吞吐量。它也是一种阻塞型的容器,适合于实现“消费者生产者”模式。
ArrayBlockingQueue是对BlockingQueue的一个数组实现,它使用一把全局的锁并行对queue的读写操作,同时使用两个Condition阻塞容量为空时的取操作和容量满时的写操作。
正因为LinkedBlockingQueue使用两个独立的锁控制数据同步,所以可以使存取两种操作并行执行,从而提高并发效率。而ArrayBlockingQueue使用一把锁,造成在存取两种操作争抢一把锁,而使得性能相对低下。LinkedBlockingQueue可以不设置队列容量,默认为Integer.MAX_VALUE.其容易造成内存溢出,一般要设置其值。
使用场景:
适用阻塞队列的好处:多线程操作共同的队列时不需要额外的同步,另外就是队列会自动平衡负载,即那边(生产与消费两边)处理快了就会被阻塞掉,从而减少两边的处理速度差距,自动平衡负载这个特性就造成它能被用于多生产者队列,因为你生成多了(队列满了)你就要阻塞等着,直到消费者消费使队列不满你才可以继续生产。 当许多线程共享访问一个公共 collection 时,ConcurrentLinkedQueue 是一个恰当的选择。
LinkedBlockingQueue 多用于任务队列(单线程发布任务,任务满了就停止等待阻塞,当任务被完成消费少了又开始负载 发布任务)
ConcurrentLinkedQueue 多用于消息队列(多个线程发送消息,先随便发来,不计并发的-cas特点)
多个生产者,对于LBQ性能还算可以接受;但是多个消费者就不行了mainLoop需要一个timeout的机制,否则空转,cpu会飙升的。LBQ正好提供了timeout的接口,更方便使用 如果CLQ,那么我需要收到处理sleep
总结
单生产者,单消费者 用 LinkedBlockingqueue
多生产者,单消费者 用 LinkedBlockingqueue
单生产者 ,多消费者 用 ConcurrentLinkedQueue
多生产者 ,多消费者 用 ConcurrentLinkedQueue
对上边总结:
如消息队列,好多client发来消息,根据client发送先后放入队列中,先发送的就先放进来,然后由于队列是先进先出,是一个一个出来的,所以不涉及到线程安全问题,所以用LinkedBlockingqueue 队列。比如还拿上边消息队列那个例子,由于队列是一个一个出来的,出来一个消息协议体就由线程池分配一个线程去处理这个消息体,这个消息体对于线程池来说谈不上共享不共享的问题,即不会多个线程去抢同一个消息体去执行,所以就不需要用线程安全的队列结构了;那假如一种情况,队列里仍然是一个一个的出来,但是出来的这个元素是 线程池共享的,即大家线程都需要用到这个从队列里出来的这个元素,也就是多消费者消费同一个东西这种情况,所以就要用线程安全的队列了,即ConcurrentLinkedQueue。