用阻塞队列LinkedBlockingQueue实现生产者消费者先进先出

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  queue=new LinkedBlockingQueue(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 queue = new ConcurrentLinkedQueue();
    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().
总结了下, 在缺乏性能测试下,对自己的编程要求更加要严格,特别是在生产环境下更是要小心谨慎。

你可能感兴趣的:(JavaBase)