<Java高级软件工程师知识结构
Java多线程是Java基础的重要的一部分,支持多线程是Java的重要特性之一. 主要包括如下内容:
- Java多线程1: 线程生命周期和多线程基础
- Java多线程2: Lock、信号量、原子量与队列
- Java多线程3: volatile
- Java多线程4: 同步锁与Java线程同步方法比较
- Java多线程5: 线程池
- Java多线程6: Java阻塞队列与生产者消费者模式
阻塞队列与阻塞栈

Java定义了阻塞队列的接口java.util.concurrent.BlockingQueue,
阻塞队列的概念是, 一个指定长度的队列, 如果队列满了, 添加新元素的操作会被阻塞等待, 直到有空位为止. 同样, 当队列为空时候, 请求队列元素的操作同样会阻塞等待, 直到有可用元素为止.
阻塞队列还有更多实现类, 用来满足各种复杂的需求:ArrayBlockingQueue, DelayQueue, LinkedBlockingQueue, PriorityBlockingQueue, SynchronousQueue , 具体的API差别也很小.
对于阻塞栈, 与阻塞队列相似. 不同点在于栈是“后入先出”的结构, 每次操作的是栈顶, 而队列是“先进先出”的结构, 每次操作的是队列头.
Java为阻塞栈定义了接口:java.util.concurrent.BlockingDeque
1、核心方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public interface BlockingQueue<E> extends Queue<E> {
boolean add(E e);
boolean offer(E e);
void put(E e) throws InterruptedException;
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;
E take() throws InterruptedException;
E poll(long timeout, TimeUnit unit) throws InterruptedException;
int remainingCapacity();
boolean remove(Object o);
public boolean contains(Object o);
int drainTo(Collection<? super E> c);
int drainTo(Collection<? super E> c, int maxElements); }
|
2、阻塞队列的成员
方法\处理方式 抛出异常 返回特殊值 一直阻塞 超时退出
插入方法 add(e) offer(e) put(e) offer(e,time,unit)
移除方法 remove() poll() take() poll(time,unit)
检查方法 element() peek() 不可用 不可用

3、成员详解
ArrayBlockingQueue
基于数组的阻塞队列实现,在ArrayBlockingQueue内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
ArrayBlockingQueue在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea之所以没这样去做,也许是因为ArrayBlockingQueue的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue和LinkedBlockingQueue间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的Node对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC的影响还是存在一定的区别。而在创建ArrayBlockingQueue时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。
LinkedBlockingQueue
基于链表的阻塞队列,同ArrayListBlockingQueue类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。而LinkedBlockingQueue之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
作为开发者,我们需要注意的是,如果构造一个LinkedBlockingQueue对象,而没有指定其容量大小,LinkedBlockingQueue会默认一个类似无限大小的容量(Integer.MAX_VALUE),这样的话,如果生产者的速度一旦大于消费者的速度,也许还没有等到队列满阻塞产生,系统内存就有可能已被消耗殆尽了。
DelayQueue
DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
使用场景:
DelayQueue使用场景较少,但都相当巧妙,常见的例子比如使用一个DelayQueue来管理一个超时未响应的连接队列。
PriorityBlockingQueue
基于优先级的阻塞队列(优先级的判断通过构造函数传入的Compator对象来决定),但需要注意的是PriorityBlockingQueue并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。在实现PriorityBlockingQueue时,内部控制线程同步的锁采用的是公平锁。
SynchronousQueue
一种无缓冲的等待队列,类似于无中介的直接交易,有点像原始社会中的生产者和消费者,生产者拿着产品去集市销售给产品的最终消费者,而消费者必须亲自去集市找到所要商品的直接生产者,如果一方没有找到合适的目标,那么对不起,大家都在集市等待。相对于有缓冲的BlockingQueue来说,少了一个中间经销商的环节(缓冲区),如果有经销商,生产者直接把产品批发给经销商,而无需在意经销商最终会将这些产品卖给那些消费者,由于经销商可以库存一部分商品,因此相对于直接交易模式,总体来说采用中间经销商的模式会吞吐量高一些(可以批量买卖);但另一方面,又因为经销商的引入,使得产品从生产者到消费者中间增加了额外的交易环节,单个产品的及时响应性能可能会降低。
声明一个SynchronousQueue有两种不同的方式,它们之间有着不太一样的行为。公平模式和非公平模式的区别:
如果采用公平模式:SynchronousQueue会采用公平锁,并配合一个FIFO队列来阻塞多余的生产者和消费者,从而体系整体的公平策略;
但如果是非公平模式(SynchronousQueue默认):SynchronousQueue采用非公平锁,同时配合一个LIFO队列来管理多余的生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。
4、秒杀demo
秒杀对列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| package com.hsshy.beam.queue.jvm;
import com.hsshy.beam.queue.entity.SuccessKilled;
import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
public class SeckillQueue { static final int QUEUE_MAX_SIZE = 100; static BlockingQueue<SuccessKilled> blockingQueue = new LinkedBlockingQueue<SuccessKilled>(QUEUE_MAX_SIZE);
private SeckillQueue(){};
private static class SingletonHolder{
private static SeckillQueue queue = new SeckillQueue(); } public static SeckillQueue getMailQueue(){ return SingletonHolder.queue; }
public Boolean produce(SuccessKilled kill) throws InterruptedException { return blockingQueue.offer(kill); }
public SuccessKilled consume() throws InterruptedException { return blockingQueue.take(); } public int size() { return blockingQueue.size(); } }
|
springboot启动时执行类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| package com.hsshy.beam.queue.jvm;
import com.hsshy.beam.queue.entity.SuccessKilled; import com.hsshy.beam.queue.service.ISeckillService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.stereotype.Component;
@Component public class TaskRunner implements ApplicationRunner { @Autowired private ISeckillService seckillService; @Override public void run(ApplicationArguments var) throws Exception{ while(true){ SuccessKilled kill = SeckillQueue.getMailQueue().consume(); if(kill!=null){ seckillService.startSeckil(kill.getId(), kill.getUserId()); } } } }
|
controller层
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| @ApiOperation(value="秒杀柒(进程内队列)",nickname="科帮网") @PostMapping("/startQueue") public R startQueue(long seckillId){ seckillService.deleteSeckill(seckillId); final long killId = seckillId; LOGGER.info("开始秒杀柒(正常)"); for(int i=0;i<1000;i++){ final long userId = i; Runnable task = new Runnable() { @Override public void run() { SuccessKilled kill = new SuccessKilled(); kill.setId(killId); kill.setUserId(userId); try { Boolean flag = SeckillQueue.getMailQueue().produce(kill); if(flag){ LOGGER.info("用户:{}{}",kill.getUserId(),"秒杀成功"); }else{ LOGGER.info("用户:{}{}",userId,"秒杀失败"); } } catch (InterruptedException e) { e.printStackTrace(); LOGGER.info("用户:{}{}",userId,"秒杀失败"); } } }; executor.execute(task); } try { Thread.sleep(10000); Long seckillCount = seckillService.getSeckillCount(seckillId); LOGGER.info("一共秒杀出{}件商品",seckillCount); } catch (InterruptedException e) { e.printStackTrace(); } return R.ok(); }
|
5、延时队列demo
消息体
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| package com.hsshy.beam.queue.delay; import java.util.concurrent.Delayed; import java.util.concurrent.TimeUnit;
public class Message implements Delayed { private int id; private String body; private long excuteTime; public int getId() { return id; } public String getBody() { return body; } public long getExcuteTime() { return excuteTime; } public Message(int id, String body, long delayTime) { this.id = id; this.body = body; this.excuteTime = TimeUnit.NANOSECONDS.convert(delayTime, TimeUnit.MILLISECONDS) + System.nanoTime(); } @Override public int compareTo(Delayed delayed) { Message msg = (Message) delayed; return Integer.valueOf(this.id) > Integer.valueOf(msg.id) ? 1 : (Integer.valueOf(this.id) < Integer.valueOf(msg.id) ? -1 : 0); } @Override public long getDelay(TimeUnit unit) { return unit.convert(this.excuteTime - System.nanoTime(), TimeUnit.NANOSECONDS); } }
|
消费者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| package com.hsshy.beam.queue.delay; import java.util.concurrent.DelayQueue; public class Consumer implements Runnable { private DelayQueue<Message> queue; public Consumer(DelayQueue<Message> queue) { this.queue = queue; } @Override public void run() { while (true) { try { Message take = queue.take(); System.out.println("消费消息id:" + take.getId() + " 消息体:" + take.getBody()); } catch (InterruptedException e) { e.printStackTrace(); } } } }
|
测试类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| package com.hsshy.beam.queue.delay; import java.util.concurrent.DelayQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class DelayQueueTest { public static void main(String[] args) { DelayQueue<Message> queue = new DelayQueue<Message>(); Message m1 = new Message(1, "world", 3000); Message m2 = new Message(2, "hello", 10000); queue.offer(m2); queue.offer(m1); ExecutorService exec = Executors.newFixedThreadPool(1); exec.execute(new Consumer(queue)); exec.shutdown(); } }
|
【参考文献】:
https://www.cnblogs.com/KingIceMou/p/8075343.html
http://www.cnblogs.com/WangHaiMing/p/8798709.html
https://gitee.com/52itstyle/spring-boot-seckill
BlockingQueue(阻塞队列)详解
ArrayBlockingQueue源码解析
Java中的阻塞队列