为什么要用阻塞队列?有什么好?
阻塞就是线程挂起,当满足条件后,又被唤醒。
为什么需要BlockingQueue?
- 不再需要关心线程阻塞和唤醒的时机,因为BlockQueue包办了这个细节
- 在阻塞队列出现前,程序员必须手动控制这些细节,兼顾效率和线程安全,增加了开发难度
实现类
- *ArrayBlockingQueue:由数组结构组成的有界阻塞队列
- *LinkedBlockingQueue: 由链表结构组成的有界阻塞队列(但默认大小为Integer.MAX_VALUE,大小配置可选)
- PriorityBlockingQueue: 支持优先级排序的无界阻塞队列
- DelayQueue: 使用优先级队列实现的延迟无界阻塞队列(内部采用PriorityQueue(排序)与ReentrantLock(锁)实现)
- *SynchronousQueue: 队列只插入一个元素,同步队列
- LinkedBlockingDeque:由链表结构组成的双向阻塞队列
带*是重要的,是线程池的底层实现。
BlockQueue的一些方法
添加元素到队列中,区别就在于当队列已经满的时候,此时
public boolean add(E e) 方法将抛出IllegalStateException异常,说明队列已满。
public boolean offer(E e) 方法则不会抛异常,只会返回boolean值,告诉你添加成功与否,队列已满,当然返回false。
public void put(E e) throws InterruptedException 方法则一直阻塞(即等待,直到元素可以加入队列为止)
同理remove,poll, take三种移除队列中线程的方法只在队列为空的时候有区别, remove为抛异常,poll为返回boolean值, take等待直到有线程可以被移除。
如下图:
三套操作:插入、移除、检查。
四套处理:抛异常,返回特殊值,阻塞,超时
生产者消费者模式的传统版实现?(synchronized版和Lock版)
synchronized版本
package com.yc.blockQueue;
public class ProdConsumer_demo1 {
public static void main(String[] args) {
Number1 number = new Number1();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
number.plus();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
number.reduce();
}
}, "B").start();
}
}
class Number1 {
private int num = 0;
public synchronized void plus() {
//使用while 防止线程虚假唤醒 当线程被唤醒后 需要重新验证程序
while (num >= 1) {
System.out.println("商品已满");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
System.out.println(Thread.currentThread().getName() + "plus:" + num);
//notify表示唤醒一个线程,,notifyAll也表示唤醒一个线程,但它会notify所有的线程,具体唤醒哪一个线程,由jvm来决定
this.notifyAll();
}
public synchronized void reduce() {
//使用while 防止线程虚假唤醒 当线程被唤醒后 需要重新验证程序
while (num <= 0) {
System.out.println("缺货");
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println(Thread.currentThread().getName() + "reduce:" + num);
this.notifyAll();
}
}
lock版本:
package com.yc.blockQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ProdConsumer_demo2 {
public static void main(String[] args) {
Number2 number = new Number2();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
number.plus();
}
}, "A").start();
new Thread(() -> {
for (int i = 0; i < 20; i++) {
number.reduce();
}
}, "B").start();
}
}
class Number2 {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
private int num = 0;
public void plus() {
lock.lock();
try {
//使用while 防止线程虚假唤醒 当线程被唤醒后 需要重新验证程序
while (num >= 1) {
System.out.println("商品已满");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num++;
System.out.println(Thread.currentThread().getName() + "plus:" + num);
//signal是唤醒其中一个等待唤醒的线程,signalAll()是唤醒所有的。
condition.signalAll();
} catch (Exception e) {
} finally {
lock.unlock();
}
}
public void reduce() {
//使用while 防止线程虚假唤醒 当线程被唤醒后 需要重新验证程序
lock.lock();
try {
while (num <= 0) {
System.out.println("缺货");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
num--;
System.out.println(Thread.currentThread().getName() + "reduce:" + num);
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
生产者消费者模式的阻塞队列版实现?
package com.yc.shangguigu;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
class MyResource{
//多线程下的交互 需要可见性volatile
private volatile boolean flag=true;// 默认开始 进行生产+消费
//多线程下不用++ 线程不安全
private AtomicInteger atomicInteger =new AtomicInteger();
//进行接口适配 要求任何实现此接口的类 都能使用
BlockingQueue<String> blockingQue=null;
//传接口 不能传具体的实现类
public MyResource(BlockingQueue<String> blockingQue) {
this.blockingQue = blockingQue;
System.out.println("日志:"+blockingQue.getClass().getName());
}
public void myPro() throws Exception{
String data=null;
boolean retValue;
while(flag){
//Atomically increments by one the current value.
data=atomicInteger.incrementAndGet()+"";
retValue=blockingQue.offer(data,2L,TimeUnit.SECONDS);
if(retValue){
System.out.println(Thread.currentThread().getName()+"插入成功");
}else{
System.out.println(Thread.currentThread().getName()+"插入失败");
}
TimeUnit.SECONDS.sleep(1);
}
System.out.println(Thread.currentThread().getName()+"大老板叫停 生产动作结束");
}
public void myCon() throws Exception{
String result=null;
while(flag){
//2秒钟等不到我就不取了
result=blockingQue.poll(2,TimeUnit.SECONDS);
if(null==result||result.equalsIgnoreCase("")){
flag=false;
System.out.println(Thread.currentThread().getName()+"超时2秒钟没有取到蛋糕 我退出");
//如果不返回 消费者还会再取一次 会出现null值
return ;
}
System.out.println(Thread.currentThread().getName()+"获取成功: "+result);
}
}
public void stop(){
this.flag=false;
}
}
/*使用阻塞队列 不需要另外加lock 获sychronized
* 我们不需要关心什么时候需要阻塞线程,什么是够需要唤醒线程,因为这一切BlockingQueue都已经一手包办了
*
* */
public class ProAndCon3_Blockqueue {
public static void main(String[] args) {
MyResource mydata=new MyResource(new ArrayBlockingQueue<>(10));
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"生产线程启动");
try {
mydata.myPro();
} catch (Exception e) {
e.printStackTrace();
}
},"product").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"消费线程启动");
System.out.println();
System.out.println();
try {
mydata.myCon();
} catch (Exception e) {
e.printStackTrace();
}
},"consume").start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("5秒鈡時間到 老板叫停");
mydata.stop();
}
}