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)的实现原理
- Java数据结构之阻塞队列(3)
- java 之 阻塞队列实现
- 【Java】并发之阻塞队列
- Java多线程之阻塞队列
- JAVA阻塞队列之ArrayBlockingQueue
- Java并发之阻塞队列
- Java阻塞队列之BlockingQueue
- java 多线程之-阻塞队列
- Java并发之阻塞队列
- java数据结构之队列
- Java 数据结构之队列
- 不惑JAVA之JAVA基础 - 阻塞队列
- Java多线程之可阻塞的队列
- Java 使用线程经验之阻塞队列
- Java的多线程之阻塞队列
- Java并发之阻塞队列(一)
- Java并发之阻塞队列(二)
- Java并发之阻塞队列(三)
- PHP_notes_03
- 修改CentOS7的主机名以及密码
- czl蒟蒻的模板库8——单调队列
- 使用第三方APPKey授权 跳转登录 QQ
- 一代、二代、三代测序技术原理与比较
- Java数据结构之阻塞队列(3)
- PHP_notes_04
- AngularJS指令
- PHP二维数组(或任意维数组)转换成一维数组的方法汇总
- effective C++ 学习(Inheritance and Object-Oriented Design)
- 关于防止SSL劫持的解决方案
- HDFS
- 成为UX设计师:你需要知道的六个基本步骤
- Range Sum Query