Java数据结构之阻塞队列(3)

1.阻塞队列的使用

阻塞队列 (BlockingQueue)是Java util.concurrent包下重要的数据结构,BlockingQueue提供了线程安全的队列访问方式:当阻塞队列进行插入数据时,如果队列已满,线程将会阻塞等待直到队列非满;从阻塞队列取数据时,如果队列已空,线程将会阻塞等待直到队列非空。并发包下很多高级同步类的实现都是基于BlockingQueue实现的。

操作Api:

可以看得出来,使用与多线程安全的且具有阻塞功能的是

  • Put()
  • take()

当然BlockingQueue只是接口,我们来看看具体的类结构

我们使用的是他的子类

BlockingQueue 的实现类

BlockingQueue 是个接口,你需要使用它的实现之一来使用BlockingQueue,java.util.concurrent包下具有以下 BlockingQueue 接口的实现类:

  • ArrayBlockingQueue: 是一个有界的阻塞队列,其内部实现是将对象放到一个数组里。有界也就意味着,它不能够存储无限多数量的元素。它有一个同一时间能够存储元素数量的上限。你可以在对其初始化的时候设定这个上限,但之后就无法对这个上限进行修改了(译者注:因为它是基于数组实现的,也就具有数组的特性:一旦初始化,大小就无法修改)。
  • DelayQueue: 对元素进行持有直到一个特定的延迟到期。注入其中的元素必须实现 java.util.concurrent.Delayed 接口。
  • LinkedBlockingQueue:LinkedBlockingQueue 内部以一个链式结构(链接节点)对其元素进行存储。如果需要的话,这一链式结构可以选择一个上限。如果没有定义上限,将使用 Integer.MAX_VALUE 作为上限。
  • PriorityBlockingQueue: 是一个无界的并发队列。它使用了和类 java.util.PriorityQueue 一样的排序规则。你无法向这个队列中插入 null 值。所有插入到 PriorityBlockingQueue 的元素必须实现 java.lang.Comparable 接口。因此该队列中元素的排序就取决于你自己的 Comparable 实现。
  • SynchronousQueue: 是一个特殊的队列,它的内部同时只能够容纳单个元素。如果该队列已有一元素的话,试图向队列中插入一个新元素的线程将会阻塞,直到另一个线程将该元素从队列中抽走。同样,如果该队列为空,试图向队列中抽取一个元素的线程将会阻塞,直到另一个线程向队列中插入了一条新的元素。据此,把这个类称作一个队列显然是夸大其词了。它更多像是一个汇合点。

其中LinkedBlockingQueue比较常用,其他的你留个印象就好了,我们先来用例子看看阻塞队列到底是啥

阻塞队列的最常使用的例子就是生产者消费者模式,也是各种实现生产者消费者模式方式中首选的方式

代码大家最后自己在idea中运行一下,看看代码,得出结果,这样印象比较深刻

生产者

生产者每秒生产一个放进阻塞队列,其中设置一个标识符控制线程技术


    public static class Producer implements Runnable {
        private final BlockingQueue<Integer> blockingQueue;
        private volatile boolean flag;
        private Random random;

        public Producer(BlockingQueue<Integer> blockingQueue) {
            this.blockingQueue = blockingQueue;
            flag = false;
            random = new Random();

        }

        public void run() {
            while (!flag) {
                int info = random.nextInt(100);
                try {
                    blockingQueue.put(info);
                    System.out.println(Thread.currentThread().getName() + " produce " + info);
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

        public void shutDown() {
            flag = true;
        }
    }
消费者

消费者不睡眠线程,直接一直从阻塞队列中获取

    public static class Consumer implements Runnable {
        private final BlockingQueue<Integer> blockingQueue;
        private volatile boolean flag;

        public Consumer(BlockingQueue<Integer> blockingQueue) {
            this.blockingQueue = blockingQueue;
        }

        public void run() {
            while (!flag) {
                int info;
                try {
                    info = blockingQueue.take();
                    System.out.println(Thread.currentThread().getName() + " consumer " + info);

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

        public void shutDown() {
            flag = true;
        }
    }
测试用例
    public static void main(String[] args) {
        BlockingQueue<Integer> blockingQueue = new LinkedBlockingQueue<Integer>(10);
        Producer producer = new Producer(blockingQueue);
        Consumer consumer = new Consumer(blockingQueue);
        //创建1个生产者,1个消费者
        for (int i = 0; i < 2; i++) {
            if (i < 1) {
                new Thread(producer, "producer" + i).start();
            } else {
                new Thread(consumer, "consumer" + (i - 1)).start();
            }
        }

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        producer.shutDown();
        consumer.shutDown();
    }
结果
producer0 produce 27
consumer0 consumer 27
producer0 produce 29
consumer0 consumer 29
consumer0 consumer 53
producer0 produce 53
consumer0 consumer 97
producer0 produce 97
producer0 produce 32
consumer0 consumer 32

结果可以看出来,消费者只能一直阻塞着等待产品放进阻塞队列,才能消费

阻塞队列中的阻塞原理

一看到阻塞,我们就猜,为啥能让线程在那里一直等到阻塞队列有值

关键是停,能做到这个的就是有(lock)加锁

2.Lock

Java 并发包下的提供Lock,Lock相对于Synchronized可以更好的解决线程同步问题,更加的灵活和高效,并且ReadWriteLock锁还能实现读、写的分离。但线程间仅仅互斥是不够的,还需要通信,本篇的内容是基于上篇之上,使用Lock如何处理线程通信。阻塞队列(BlockingQueue)就是使用condition的和lock实现的

2.1 Object中控制线程状态方法

再开始正文之前,我们开始说说几个基础知识如

  • wait()
  • notify()
  • notifyAll()
2.2 用途
  • 如果对象调用了wait方法就会使持有该对象的线程把该对象的控制权交出去,然后处于等待状态
  • 如果对象调用了notify方法就会通知某个正在等待这个对象的控制权的线程可以继续运行。
  • 如果对象调用了notifyAll方法就会通知所有等待这个对象控制权的线程继续运行。
2.3 线程取得控制权的方法
  • 执行对象的某个同步实例方法。
  • 执行对象对应类的同步静态方法。
  • 执行对该对象加同步锁的同步块。

详细的请看简实例说明wait、notify、notifyAll的使用方法

2.4 Condition

Condition 将 Object的通信方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用

为每个对象提供多个等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 通信方法的使用

在Condition中:

  • 用await()替换wait()
  • 用signal()替换notify()
  • 用signalAll()替换notifyAll()

传统线程的通信方式,Condition都可以实现,这里注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用new Condition()方法。

Condition的强大之处在于它可以为多个线程间建立不同的Condition

使用synchronized/wait()只有一个阻塞队列,notifyAll会唤起所有阻塞队列下的线程,而使用lock/condition,可以实现多个阻塞队列,signalAll只会唤起某个阻塞队列下的阻塞线程

2.4.1 synchronized/wait()实现生产者消费者模式如下
        class Buffer {
            private int maxSize;
            private List<Date> storage;
            Buffer(int size){
                maxSize=size;
                storage=new LinkedList<>();
            }
            //生产方法
            public synchronized void put()  {
                try {
                    while (storage.size() ==maxSize ){//如果队列满了
                        System.out.print(Thread.currentThread().getName()+": wait \n");;
                        wait();//阻塞线程
                    }
                    storage.add(new Date());
                    System.out.print(Thread.currentThread().getName()+": put:"+storage.size()+ "\n");
                    Thread.sleep(1000);
                    notifyAll();//唤起线程
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }       
            }
            //消费方法
            public synchronized void take() {
                try { 
                    while (storage.size() ==0 ){//如果队列满了
                        System.out.print(Thread.currentThread().getName()+": wait \n");;
                        wait();//阻塞线程
                    }
                    Date d=((LinkedList<Date>)storage).poll();
                    System.out.print(Thread.currentThread().getName()+": take:"+storage.size()+ "\n");
                    Thread.sleep(1000);
                    notifyAll();//唤起线程
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }       
            } 
    }
    //生产者
    class Producer implements Runnable{
        private Buffer buffer;
        Producer(Buffer b){
            buffer=b;
        }
        @Override
        public void run() {
            while(true){
                buffer.put();
            }
        }   
    }
    //消费者
    class Consumer implements Runnable{
        private Buffer buffer;
        Consumer(Buffer b){
            buffer=b;
        }
        @Override
        public void run() {
            while(true){
                buffer.take();
            }
        }   
    }
    //
    public class Main{
        public static void main(String[] arg){
            Buffer buffer=new Buffer(10);
            Producer producer=new Producer(buffer);
            Consumer consumer=new Consumer(buffer);
            //创建线程执行生产和消费
            for(int i=0;i<3;i++){
                new Thread(producer,"producer-"+i).start();
            }
            for(int i=0;i<3;i++){
                new Thread(consumer,"consumer-"+i).start();
            }
        }
    }

大家可以用idea自己运行一下代码,理解一下synchronized/wait()的用法

其实写到这里 阻塞队列已经实现了,只不过把LinkedList改成队列,对应入队和出对都稍微改一下,不过我们再看看lock/condition

使用lock/condition实现生产者消费者模式如下
class Buffer {
    private  final Lock lock;
    private  final Condition notFull;
    private  final Condition notEmpty;
    private int maxSize;
    private List<Date> storage;
    Buffer(int size){
        //使用锁lock,并且创建两个condition,相当于两个阻塞队列
        lock=new ReentrantLock();
        notFull=lock.newCondition();
        notEmpty=lock.newCondition();
        maxSize=size;
        storage=new LinkedList<>();
    }
    public void put()  {
        lock.lock();
        try {   
            while (storage.size() ==maxSize ){//如果队列满了
                System.out.print(Thread.currentThread().getName()+": wait \n");;
                notFull.await();//阻塞生产线程
            }
            storage.add(new Date());
            System.out.print(Thread.currentThread().getName()+": put:"+storage.size()+ "\n");
            Thread.sleep(1000);         
            notEmpty.signalAll();//唤醒消费线程
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally{   
            lock.unlock();
        }
    }

    public  void take() {       
        lock.lock();
        try {  
            while (storage.size() ==0 ){//如果队列满了
                System.out.print(Thread.currentThread().getName()+": wait \n");;
                notEmpty.await();//阻塞消费线程
            }
            Date d=((LinkedList<Date>)storage).poll();
            System.out.print(Thread.currentThread().getName()+": take:"+storage.size()+ "\n");
            Thread.sleep(1000);         
            notFull.signalAll();//唤醒生产线程

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

class Producer implements Runnable{
    private Buffer buffer;
    Producer(Buffer b){
        buffer=b;
    }
    @Override
    public void run() {
        while(true){
            buffer.put();
        }
    }   
}
class Consumer implements Runnable{
    private Buffer buffer;
    Consumer(Buffer b){
        buffer=b;
    }
    @Override
    public void run() {
        while(true){
            buffer.take();
        }
    }   
}
public class Main{
    public static void main(String[] arg){
        Buffer buffer=new Buffer(10);
        Producer producer=new Producer(buffer);
        Consumer consumer=new Consumer(buffer);
        for(int i=0;i<3;i++){
            new Thread(producer,"producer-"+i).start();
        }
        for(int i=0;i<3;i++){
            new Thread(consumer,"consumer-"+i).start();
        }
    }
}
  • 当生产者执行put方法时,调用notEmpty.signalAll()只会唤醒notEmpty.await()下的消费者线程。
  • 当消费者执行塔克方法时,调用notFull.signalAll()只会唤醒notFull.await()下的消费者线程。
ArrayBlockingQueue源码

lock 与 lockInterruptibly比较区别在于:

  • lock 优先考虑获取锁,待获取锁成功后,才响应中断。
  • lockInterruptibly 优先考虑响应中断,而不是响应锁的普通获取或重入获取。

具体可以看
lock()与lockInterruptibly()的区别

public ArrayBlockingQueue(int capacity, boolean fair) {

        if (capacity <= 0)
            throw new IllegalArgumentException();
        //创建数组    
        this.items = new Object[capacity];
        //创建锁和阻塞条件
        lock = new ReentrantLock(fair);   
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }
//添加元素的方法
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 void enqueue(E x) {
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal();
    }
 //移除元素的方法
 public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
 //出队的方法
 private E dequeue() {
        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;

参考

Java并发编程-阻塞队列(BlockingQueue)的实现原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值