浅谈阻塞队列

引言

阻塞队列在数据结构中所起的作用大致如下图所示:
在这里插入图片描述
当阻塞队列为空时,从队列中获取元素的操作会被阻塞,直到其他线程往空队列添加新的元素。
当阻塞队列满时,往阻塞队列添加元素会被阻塞,直到其他线程从队列中移除一个元素。

为什么需要阻塞线程

在多线程领域:某些情况下会挂起线程(阻塞wait/await),一旦条件满足,被挂起的线程又会自动唤醒(notify/signal)。
使用阻塞队列的好处就是:不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,这一切BlockingQueue都一手包办了。

在Concurrent包发布前的多线程环境下,程序员需要自己去控制线程阻塞和唤醒的细节,尤其还要兼顾效率和线程安全,这势必会增加程序的复杂度。阻塞队列使编程更简单且降低了程序的复杂度。

BlockingQueue的实现及核心方法

查看类继承关系:BlockingQueue接口实现了Queue接口,Queue接口继承自Collection接口。BlockingQueue的实现类主要有以下7种:
ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
LinkedBlockingQueue:由链表结构组成的有界阻塞队列(默认值为Integer.MAX_VALUE —即2147483647,使用时一定手动指定值)
SynchronousQueue:不存储元素的阻塞队列,即单个元素的队列(每一个put操作必须等待一个take操作,否则不能继续添加元素,反之亦然。)。
PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
DelayQueue:使用优先级队列实现的延迟无界阻塞队列。
LinkedTransferQueue:由链表结构组成的无界阻塞队列。
LinkedBlockingDeque:由链表结构组成的双向阻塞队列。
在这里插入图片描述

  1. 抛出异常:
    阻塞队列满时插在往队列add抛IllegalStateException:Queue full.
    阻塞队列空时从队列remove元素抛NoSuchElementException.
  2. 特殊值:插入成功返回true失败false,移除元素成功返回移除的元素,失败返回null。
  3. 阻塞:
    阻塞队列满时,生产线程往队列put元素,队列会一直阻塞生产线程直到put数据或相应中断。
    阻塞队列空时,消费者线程试图从队列take元素,队列会一直阻塞消费线程直到队列可用。
  4. 超时:当阻塞队列满时,队列会阻塞生产者线程一定时间,超时候生产者线程会退出。

应用场景

阻塞队列主要的应用场景是:生产者消/费者模式、线程池及消息中间件,接下来分别用线程间通信及阻塞队列实现生产者/消费者模式。
线程间通信之生产者/消费者模式

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//生产者对变量加1操作,消费者对变量减1操作
public class ProduceConsumer {
	//number大于5时停止生产
	private int number = 0;
	private Lock lock = new ReentrantLock();
	Condition condition = lock.newCondition();
	
	public void produce() {
		lock.lock();
		try {
			//while防止虚假唤醒
			while(number >= 5) {
				condition.await();
			}
			number++; //生产者执行++操作
			System.out.println(Thread.currentThread().getName() + " number: " + number);
			condition.signalAll(); //唤醒其他线程
		}catch(Exception e) {
			System.out.println(Thread.currentThread().getName() + " produce failed.");
		}finally {
			lock.unlock();
		}
	}
	
	public void consumer() {
		lock.lock();
		try {
			while(number == 0) { //number为0时消费者线程阻塞
				condition.await();
			}
			number--; //消费者执行--操作
			System.out.println(Thread.currentThread().getName() + " number: " + number);
			condition.signalAll(); //唤醒其他线程
		}catch(Exception e) {
			System.out.println(Thread.currentThread().getName() + " consumer failed.");
		}finally {
			lock.unlock();
		}
	}
	
	public static void main(String[] args) {
		ProduceConsumer produceConsumer = new ProduceConsumer();
		//创建两个生产者线程
		for (int i = 0; i < 2; i++) {
			final int threadFlag = i;
			new Thread(new Runnable() {
				@Override
				public void run() {
					for (int j = 0; j < 5; j++) {
						produceConsumer.produce();
					}
				}
			},"produce" + threadFlag).start();
		}
		//创建两个消费者线程
		for (int i = 0; i < 2; i++) {
			final int threadFlag = i;
			new Thread(new Runnable() {
				@Override
				public void run() {
					for (int j = 0; j < 5; j++) {
						produceConsumer.consumer();
					}
				}
			},"consumer" + threadFlag).start();
		}
	}
}

阻塞队列之生产者/消费者模式:不需要关心什么时候阻塞或唤醒线程,这一切都由阻塞队列实现。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class ProduceConsuerBlockingQueue {
	//控制生产者启停,volatile保证可见性
	private volatile boolean flag = true;
	//多线程环境下保证原子性
	AtomicInteger automicInteger = new AtomicInteger();
	BlockingQueue<String> blockingQueue = null;
	//构造传接口--适配7种阻塞队列
	public ProduceConsuerBlockingQueue(BlockingQueue<String> blockingQueue) {
		this.blockingQueue = blockingQueue;
		//日志排查传入哪种阻塞队列
		System.out.println(blockingQueue.getClass().getName());
	}
	
	public void produce() {
		String data = null;
		boolean result;
		while(flag) {
			try {
				data = automicInteger.incrementAndGet() + "";
				result = blockingQueue.offer(data, 2, TimeUnit.SECONDS);
				System.out.println(Thread.currentThread().getName() + "生产是否成功: "+result);
				TimeUnit.SECONDS.sleep(1); //每秒生产一次
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName() + " 停止生产");
	}
	
	public void consumer() {
		String data = null;
		while(true) {
			try {
				data = blockingQueue.poll(2, TimeUnit.SECONDS);
				if(data == null) {
					System.out.println("2秒钟没有取到数据,消费退出.");
					return;
				}
				System.out.println(Thread.currentThread().getName() + "消费成功: "+data);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public void stop() {
		this.flag = false;
	}
	
	public static void main(String[] args) {
		BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(5);
		ProduceConsuerBlockingQueue pcbq = new ProduceConsuerBlockingQueue(blockingQueue);
		
		new Thread(() -> {
			System.out.println(Thread.currentThread().getName() + " 生产线程启动.");
			pcbq.produce();
		}, "procude").start();
		
		new Thread(() -> {
			System.out.println(Thread.currentThread().getName() + "消费线程启动");
			pcbq.consumer();
		}, "consumer").start();
		
		//运行5秒都停止
		try {
			TimeUnit.SECONDS.sleep(5);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println("5秒结束,活动结束");
		System.out.println();
		System.out.println();
		pcbq.stop();
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值