LinkedBlockingQueue是一个基于已链接节点的、范围任意的blocking queue的实现。 由于LinkedBlockingQueue实现是线程安全的,实现了先进先出等特性,是作为生产者消费者的首选,LinkedBlockingQueue 可以指定容量,也可以不指定,不指定的话,默认最大是Integer.MAX_VALUE,其中主要用到put和take方法,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。
此队列按 FIFO(先进先出)排序元素。队列的头部 是在队列中时间最长的元素。队列的尾部 是在队列中时间最短的元素。
新元素插入到队列的尾部,并且队列检索操作会获得位于队列头部的元素。链接队列的吞吐量通常要高于基于数组的队列,
但是在大多数并发应用程序中,其可预知的性能要低。
可选的容量范围构造方法参数作为防止队列过度扩展的一种方法。
如果未指定容量,则它等于 Integer.MAX_VALUE。除非插入节点会使队列超出容量,否则每次插入后会动态地创建链接节点。
1:如果未指定容量,默认容量为Integer.MAX_VALUE ,容量范围可以在构造方法参数中指定作为防止队列过度扩展。
2:此对象是 线程阻塞-安全的
3:不接受 null 元素
4:它实现了BlockingQueue接口。
5:实现了 Collection 和 Iterator 接口的所有可选 方法。
6:在JDK5/6中,LinkedBlockingQueue和ArrayBlocingQueue等对象的poll(long timeout, TimeUnit unit)存在内存泄露Leak的对象AbstractQueuedSynchronizer.Node,据称JDK5会在Update12里Fix,JDK6会在Update2里Fix
示例代码如下:
package com.jh.sms.test;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class TestBlockingQueue {
static BlockingQueue <Hamburger> queue=new LinkedBlockingQueue<Hamburger>(10);
public static void main(String[] args) throws InterruptedException {
Producer t1 = new Producer();
Consumer t2 = new Consumer();
t1.start();
t2.start();
System.out.println("Thread.sleep(1000)");
Thread.sleep(1000);
t2.interrupt();
}
}
class Hamburger{
int id;
public Hamburger(int id) {
this.id=id;
}
@Override
public String toString() {
return "Hamburger: "+id;
}
}
class Producer extends Thread{
@Override
public void run() {
int i=0;
while(i<10){
Hamburger e = new Hamburger(i);
try {
System.out.println("Produce Hamburger: "+i);
TestBlockingQueue.queue.put(e);
} catch (InterruptedException e1) {
System.out.println("Hamburger so many, it was closed.");
return;
}
i++;
}
}
}
class Consumer extends Thread{
@Override
public void run() {
while(true){
try {
System.out.println("Eat Hamburger: "+TestBlockingQueue.queue.take());
} catch (InterruptedException e1) {
System.out.println("Hamburger so less, It was stopped.");
return;
}
}
}
}
由于阻塞队列LinkedBlockingQueue,FIFO,使用它的put(),take()会判断当前队列是有值,即等待生产再消费,即便是两个线程并行执行,很简单方便的解决了生产者消费者问题,但是在这里需要注意的是在两个run()方法中,打印当前生产或消费Hamburger
的时候,最好把put()和take()方法放在相应的打印语句中一起执行,否则会发生先消费后生产的后果。 因为打印语句和方法的执行时两段代码,由于双线程同时执行,无法保证执行的相应代码块的顺序性!!由于最后互相等待会造成死锁,所以在主线程睡眠1秒后打断消费者,让它别等了,抛异常后return结束消费线程,最后整个main方法调用结束。
ConcurrentLinkedQueue
ConcurrentLinkedQueue是Queue的一个安全实现.Queue中元素按FIFO原则进行排序.采用CAS操作,来保证元素的一致性。
LinkedBlockingQueue是一个线程安全的阻塞队列,它实现了BlockingQueue接口,BlockingQueue接口继承自java.util.Queue接口,并在这个接口的基础上增加了take和put方法,这两个方法正是队列操作的阻塞版本。
package com.jh.sms.test;
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);
}
}
/**
* 消费
*/
static class Poll implements Runnable {
public void run() {
while (queue.size()>0) {
// while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
latch.countDown();
}
}
}
运行结果:
costtime 1415ms
改用while (queue.size()>0)后
运行结果:
cost time 38214ms
结果居然相差那么大,看了下ConcurrentLinkedQueue的API原来.size()是要遍历一遍集合的,难怪那么慢,所以尽量要避免用size而改用isEmpty().
总结了下, 在缺乏性能测试下,对自己的编程要求更加要严格,特别是在生产环境下更是要小心谨慎。