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

来源:互联网 发布:部落冲突苍蝇升级数据 编辑:程序博客网 时间:2024/06/10 23:41

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 27consumer0 consumer 27producer0 produce 29consumer0 consumer 29consumer0 consumer 53producer0 produce 53consumer0 consumer 97producer0 produce 97producer0 produce 32consumer0 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)的实现原理

原创粉丝点击