什么情况下我们会使用阻塞队列:多线程并发处理,线程池!
阻塞队列 4种解决方案
方式 | 抛出异常 | 不抛异常,有返回值 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put() | offer (Timeout) |
移除 | remove | poll | take() | poll (Timeout) |
检测队首元素 | element() | peek() | / | / |
▶ 1. 抛出异常
/**
抛出异常
add:超出队列指定容量,会报:Queue full 异常
remove:(每调用一次,移除一个元素),队列值为空时继续remove会报:NoSuchElementException
*/
public static void main(String[] args){
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(2);
System.out.println(queue.add("1"));
System.out.println(queue.add("2"));
//queue.add("3"); // 使用add超出队列指定容量,会报:Queue full异常
// 获取队列首部元素 :1
System.out.println(queue.element());
System.out.println("移除元素:"+queue.remove());//队列为空用移除:NoSuchElementException
System.out.println("队列:"+queue);
}
执行结果:
把 queue.add("3"); 注释打开:添加第3个元素时,报Queue full异常
▶ 2. 不抛异常,有返回值
/**
有返回值,没有异常
offer:返回false,超出容量后不再添加入队
poll:(每调用一次,移除一个元素)队列值为空时继续poll会输出null
*/
public static void main(String[] args){
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(2);
System.out.println(queue.offer("3"));
System.out.println(queue.offer("4"));
System.out.println(queue.offer("5")); // false 超出容量后不再添加入队
// 获取队列首部元素 :3
System.out.println(queue.peek());
System.out.println("移除元素:"+queue.poll());
System.out.println("移除元素:"+queue.poll());
System.out.println("移除元素: "+queue.poll()); // 队列值为空时继续poll会输出null
System.out.println("队列:"+queue);
}
执行结果:1. 队列已满,继续offer返回false 2. 空队列poll 返回null
▶ 3. 阻塞等待
/**
等待,阻塞(一直等待)
put/take:超出长度无法添加,等待阻塞 / 队列内没有元素,等待阻塞
———— 直到添加成功或删除成功,程序才会结束
*/
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(2);
queue.put("a");
queue.put("b");
//queue.put("c"); // 等待阻塞,直到添加成功,程序才会结束
System.out.println("移除元素:"+queue.take());
System.out.println("移除元素:"+queue.take());
System.out.println("移除元素:"+queue.take()); // 等待阻塞,直到队内有元素可删除,程序才结束
System.out.println("队列:"+queue);
}
执行结果:
▶ 4. 超时等待
/**
等待,阻塞(超时等待)
offer/poll:超出指定时间,还未操作成功,则忽略此次添加/删除
*/
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(2);
queue.offer("A");
queue.offer("B");
queue.offer("C",2, TimeUnit.SECONDS); // 超时2秒后,还未添加成功,则忽略此次add
System.out.println("移除元素:"+queue.poll());
System.out.println("移除元素:"+queue.poll());
System.out.println("移除元素: "+queue.poll(2, TimeUnit.SECONDS)); // 同offer
System.out.println("队列:"+queue);
}
执行结果:2秒后添加失败 或 无元素可删除,则忽略,继续执行下列代码
扩展
/ | ArrayBlockingQueue | LinkedBlockingQueue |
---|---|---|
阻塞实现方式 | 通知模式实现 | 通知模式实现 |
是否有界 | 有界,必须指定初始化大小 | 有界,构造器可以不初始化,默认为INTEGER.MAX_VALUE |
存取元素是否会创建和销毁 | 不会 | 会 |
生产者消费者锁的使用情况 | 使用一把锁 | 各自使用不同的锁 |
☛ ArrayBlockingQueue 数组队列
基于数组实现的阻塞队列,创建对象时必须指定容量大小,且可指定是否公平(默认非公平,即不保证等待时间最长的队列最优先能够访问队列),存取元素的阻塞方法put/take,使用通知模式来实现。生产者和消费者存取数据用的同一把重入锁,无法真正实现生产者和消费者的并行
☛ LinkedBlockingQueue 链表队列
基于链表实现,创建时如不指定大小,默认为Integet.MAX_VALUE,如果生产者的速度远大于消费者速度,则链表会堆积请问,造成内存压力,易产生OOM,其次是基于链表,每次放入元素会构造一个新节点对象,大量并发下可能会对GC造成一定影响。同样使用通知模式来实现,生产者和消费者分别使用两把重入锁实现同步,提高系统并发度
☛ SynchronousQueue同步队列
特殊的阻塞队列,容量为0,也无法设置容量大小。put了一个元素,必须等待消费者线程take取出来,才能再进行下一个元素的插入操作
类似一个中介,从生产者手中拿到任务直接转交给消费者,直接传递机制,而非存储元素
public static void main(String[] args) {
// 1.初始化同步队列
BlockingQueue<String> queue = new SynchronousQueue<String>();
// 2.定义2个线程,一个线程存,一个线程取
new Thread(()->{
try {
// 添加3个元素
System.out.println(Thread.currentThread().getName()+":put 1");
queue.put("1");
System.out.println(Thread.currentThread().getName()+":put 2");
queue.put("2");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程A").start();
new Thread(()->{
try {
// 每隔3秒取出一个元素
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+queue.take());
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"=>"+queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程B").start();
}
执行结果:put一个元素,须等待消费者线程取出来,下一个元素才能put进去