阻塞队列
第一、生产者消费者的实际的应用
使用的分布式消息队列,比如ActiveMQ、RabbitMQ等等,消息队列的是有可以使得程序之间解耦,提升程序响应效率。
如果我们把多线程环境比作是分布式的话,那么线程与线程之间也可以用这种消息队列的方式进行数据通信和解耦。
第二、阻塞队列的使用案例
注册成功之后新增积分
假如我们模拟一个场景,就是用户注册的时候,在注册的时候成功以后发放积分。这个场景一般来说,会这么去实现
但是实际上,我们需要考虑两个问题
1. 性能,在注册这个环节里面,假如添加用户需要花费 1 秒钟,增加积分需要花费 1 秒钟,那么整个注册结果的返回就可能需要大于 2 秒,虽然影响不是很大,
但是在量比较大的时候,也需要做一些优化
2. 耦合,添加用户和增加积分,可以认为是两个领域,也就是说,增加积分并不是注册必须要具备的功能,但是一旦增加积分这个逻辑出现异常,就会导致注册失败。
这种耦合在程序设计的时候是一定要规避的因此可以通过异步的方式来实现
3.代码实现
public class User {
private String userName;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
@Override
public String toString() {
return "User{" + "userName='" + userName + '\'' + '}';
}
}
public class UserService<T> {
private final ExecutorService single =
Executors.newSingleThreadExecutor();
private volatile boolean isRunning = true;
BlockingQueue<T> queue = new LinkedBlockingQueue(3);
{
init();
}
public void init(){
single.execute(()->{
while (isRunning){
try {
//阻塞获取
User user = (User) queue.poll(2, TimeUnit.SECONDS);
if(user==null){
isRunning = false;
break;
}
sendPoints(user);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
public void register(){
User user = new User();
user.setUserName("name");
addUser(user);
}
/**
* 添加用户
* @param user
*/
private void addUser(User user) {
System.out.println("添加用户成功"+user);
}
/**
* 添加积分成功
* @param user
*/
private void sendPoints(User user){
System.out.println("添加积分成功"+user);
}
}
public class Test001 {
public static void main(String[] args) {
UserService userService = new UserService();
userService.register();
}
}
第三.阻塞队列的应用场景
阻塞队列这块的应用场景,比较多的仍然是对于生产者消费者场景的应用,但是由于分布式架构的普及,更多的关注在分布式消息队列上。所以其实如果把阻塞队
列比作成分布式消息队列的话,那么所谓的生产者和消费者其实就是基于阻塞队列的解耦。另外,阻塞队列是一个 fifo 的队列,所以对于希望在线程级别需要实现对
目标服务的顺序访问的场景中,也可以使用
第四.JUC中的阻塞队列
4.1阻塞队列定义
阻塞队列,顾名思义,首先是一个队列,而一个阻塞队列在数据结构中起的作用大致如下图:
- 当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。
当阻塞队列是满时,从队列里添加元素的操作将会被阻塞。
- 试图从空的阻塞队列中获取元素的线程将会被阻塞,知道其他线程往空的队列插入新的元素。
同样
试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他线程从列中移除
一个或者多个元素或者完全清空队列使队列重新变得空闲起来并后续新增
4.2阻塞队列的好处
在多线程领域:所谓阻塞,在某些情况下会被挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒。
为什么需要BlockingQueue
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockQueue都给你一手包办了,
在concurrent包发布以前,在多环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,
而这些给我们的程序带来不小的复杂度。
4.3常用的阻塞队列
ArrayBlockingQueue:由数组结构组成的有界阻塞队列
LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为Integer.Max_Value)阻塞队列
SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列
4.4队列的常用方法
在阻塞队列中,提供了四种处理方式
1. 插入操作
add(e) :添加元素到队列中,如果队列满了,继续插入元素会报错,IllegalStateException。
offer(e) : 添加元素到队列,同时会返回元素是否插入成功的状态,
如果成功则返回 trueput(e) :当阻塞队列满了以后,生产者继续通过 put添加元素,队列会一直阻塞生产者线程,
知道队列可用offer(e,time,unit) :当阻塞队列满了以后继续添加元素,
生产者线程会被阻塞指定时间,如果超时,则线程直接退出
2. 移除操作
remove():当队列为空时,调用 remove 会返回 false,如果元素移除成功,则返回 true
poll(): 当队列中存在元素,则从队列中取出一个元素,如果队列为空,则直接返回 null
take():基于阻塞的方式获取队列中的元素,如果队列为空,则 take 方法会一直阻塞,直到队列中有新的数据可以消费
poll(time,unit):带超时机制的获取数据,如果队列为空,则会等待指定的时间再去获取元素返回
4.5LinkedBlockingQueue原理分析
public boolean offer(E e) {
//1、如果元素为null,抛出空指针异常
if (e == null) throw new NullPointerException();
//2、如果当前的队列的满了,则丢弃要放入的元素,返回false
final AtomicInteger count = this.count;
if (count.get() == capacity)
return false;
int c = -1;
//3、构造新节点,获取putLock锁
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
//如果队列没有满,则进队列,并递增元素计较
if (count.get() < capacity) {
enqueue(node);
c = count.getAndIncrement();
if (c + 1 < capacity)
notFull.signal();
}
} finally {
putLock.unlock();
}
if (c == 0)
signalNotEmpty();
return c >= 0;
}
这段代码做了几个事情
1. 判断添加的数据是否为空
2. 添加重入锁
3. 判断队列长度,如果队列长度等于数组长度,表示满了直接返回 false
4. 否则,直接调用 enqueue 将元素添加到队列中
4.6生产者与消费者案例
生产者:
/**
* 生产者
*/
public class ProducerThread implements Runnable {
private BlockingQueue<String> queue;
private String data;
public ProducerThread(BlockingQueue<String> queue,String data) {
this.queue = queue;
this.data = data;
}
@Override
public void run() {
System.out.println("生产者启动======");
boolean offer = queue.offer(data);
if (offer) {
System.out.println(Thread.currentThread().getName()
+"生产队列成功"+data+"成功");
}else{
System.out.println(Thread.currentThread().getName()
+"生产队列成功"+data+"失败");
}
System.out.println("生产者关闭");
}
}
消费者
/**
* 消费者
*/
public class ConsumerThread implements Runnable {
private BlockingQueue<String> queue;
private boolean flag = true;
public ConsumerThread(BlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while (flag){
System.out.println("消费者启动======");
String data = null;
try {
data = queue.poll();
if(data!=null){
System.out.println("消费者队列成功"+data);
}else{
System.out.println("消费队列失败");
flag = false;
break;
}
} catch (Exception e) {
e.printStackTrace();
}finally {
}
}
}
}
测试:
public class Test001 { public static void main(String[] args) { BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(3); new Thread(new ProducerThread(blockingQueue,"A")).start(); new Thread(new ConsumerThread(blockingQueue)).start(); } }