Java并发编程 | 第三篇:数据共享通道之BlockingQueue

在新增的Concurrent包中,BlockingQueue很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。

1、 认识BlockingQueue

阻塞队列,顾名思义,首先它是一个队列,而一个队列在数据结构中所起的作用大致如下图所示:

从上图我们可以很清楚看到,通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出;
常用的队列主要有以下两种:(当然通过不同的实现方式,还可以延伸出很多不同类型的队列,DelayQueue就是其中的一种)
  先进先出(FIFO):先插入的队列的元素也最先出队列,类似于排队的功能。从某种程度上来说这种队列也体现了一种公平性。
  后进先出(LIFO):后插入队列的元素最先出队列,这种队列优先处理最近发生的事件。


下面两幅图演示了BlockingQueue的两个常见阻塞场景:
       如上图所示:当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。

   如上图所示:当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。
这也是我们在多线程环境下,为什么需要BlockingQueue的原因。作为BlockingQueue的使用者,我们再也不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了。

2、BlockingQueue具体实现

首先BlockingQueue是一个接口,它的实现类是这些
这里写图片描述
里面有基于数组实现的ArrayBlockingQueue,适合有界队列和基于链表实现的LinkedBlockingQueue,适合无界队列,因为长度可以动态增加

因为BlockingQueue会让服务线程(不断从队列中取任务,然后进行处理的线程)在队列为空时进行等待,当新的任务加入队列,就会唤醒线程去处理,想了解怎么实现可以看ArrayBlockingQueue源码

 /** Main lock guarding all access */
    final ReentrantLock lock;

    /** Condition for waiting takes */
    private final Condition notEmpty;

    /** Condition for waiting puts */
    private final Condition notFull;

主要看put()和take(),put()是将元素压入队列中,若队列满了,就等待直到有空闲的位置,

 public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)//当队列满的时候
                notFull.await();//等待
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

当有元素退出队列出现空位时,就通知等待入队的线程

 private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

而take()是从队列取元素,若队列为空就等待,直到有新的元素加入

public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)//当队列为空
                notEmpty.await();//等待
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

当然了,当队列有元素添加进来,就给线程一个通知

 private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal();//通知
    }

3、BlockingQueue实现

下面程序展示如何使用阻塞队列来控制线程集,程序在一个目录以及目录下的所有文件,打印出包含指定关键字的文件列表

生产者线程:枚举所有子目录下所有文件放进队列中,最后放进模拟对象,作为结束信号

public class FileEnumeratonTask implements Runnable{

    private BlockingQueue<File> queue;
    private File startingDirectory;

    //模拟对象,当搜索线程取到该对象,就发出完成信号,将其返回并终止
    public static File DUMMY=new File("");


    public FileEnumeratonTask(BlockingQueue<File> queue, File startingDirectory) {

        this.queue = queue;
        this.startingDirectory = startingDirectory;
    }

    @Override
    public void run() {
        try {

            enumerate(startingDirectory);
            queue.put(DUMMY);//最后放入模拟对象,标志工作结束

        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
    //把所有文件放入阻塞队列
    public void enumerate(File directory) throws InterruptedException{
        File[] files=directory.listFiles();
        for(File file:files){
            if(file.isDirectory())//如果是目录就递归,是文件就放入阻塞队列
                enumerate(file);
            else
                queue.put(file);
        }
    }

}

消费者线程:即搜索线程,每个搜索线程在队列取一个文件,打开它,打印包含关键字的所有行

public class SearchTask implements Runnable {
    private BlockingQueue<File> queue;
    private String keyword;

    public SearchTask(BlockingQueue<File> queue, String keyword) {
        super();
        this.queue = queue;
        this.keyword = keyword;
    }

    @Override
    public void run() {

            try {
                boolean isDone=false;
                if(!isDone){
                    File file;
                file = queue.take();
                if(file==FileEnumeratonTask.DUMMY){
                    queue.add(file);
                    isDone=true;
                }else
                    search(file);
            }
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }


    }

    public void search(File file) {
        try {
            Scanner in=new Scanner(new FileInputStream(file));
            int lineNumber=0;
            while(in.hasNextLine()){
                lineNumber++;
                String line=in.nextLine();
                if(line.contains(keyword)){
                    System.out.printf("%s:%d:%s%n",file.getPath(),lineNumber,line);
                }

            }
            in.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }


    }
}
public class BlockingQueueTest {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
        System.out.println("enter base directory:");
        String directory=in.nextLine();
        System.out.println("enter keyword:");
        String keyword=in.nextLine();

        final int FILE_QUEUE_COUNT=10;
        final int SEARCH_COUNT=100;

        BlockingQueue<File> queue=new ArrayBlockingQueue(FILE_QUEUE_COUNT);
        FileEnumeratonTask fileEnumeratonTask=new FileEnumeratonTask(queue, new File(directory));
        new Thread(fileEnumeratonTask).start();
        for(int i=0;i<SEARCH_COUNT;i++){
            new Thread(new SearchTask(queue,keyword)).start();
        }
    }
}

这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值