阻塞队列图:
阻塞队列与普通队列的区别在于,当队列是空的时,从队列中获取元素的操作将会被阻塞,或者当队列是满时,往队列里添加元素的操作会被阻塞。试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。同样,试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程使队列重新变得空闲起来。
有好几种阻塞队列,本文只用到了最常用的两个阻塞队列之一:ArrayBlockingQueue。
ArrayBlockingQueue:基于数组实现的一个阻塞队列,在创建ArrayBlockingQueue对象时必须制定容量大小。并且可以指定公平性与非公平性,默认情况下为非公平的。
阻塞队列的场景刚好符合操作系统中的生产者消费者模型,传统的生产者消费者问题中还要在特定位置实现wait()和signal()操作,不能颠倒顺序,比较繁琐。而jdk 5.0开始引入的这个java.util.concurrent包显然让生产者消费者问题得到很好的简化,全都自动完成了,wait(),signal()都不用写了,多好。
put():用来向队尾存入元素,如果队列满,则等待;
take():用来从队首取元素,如果队列为空,则等待;
代码说明:
线程采用继承Thread类重写run方法实现。
Math.Random()产生一个0到1(不包括0和1)的随机浮点数,扩大10倍+1后结果就是产生一个1到10的随机整数,使得生产者每生产一个产品放入阻塞队列队尾(或者消费者从阻塞队列队首取走一个产品)后对应的线程会休眠1到10ms
import java.util.concurrent.ArrayBlockingQueue;
public class Test {
private int queueSize = 10;//初始化阻塞队列长度为10
private ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(queueSize);
public static void main(String[] args) {
Test test = new Test();
Producer producer = test.new Producer();
Consumer consumer = test.new Consumer();
producer.start();
consumer.start();
}
class Consumer extends Thread{
@Override //此注解用于检测是否重写了父类的run方法,如果下行的run单词写错编译器就会报红
public void run() {
consume();
}
private void consume() {
int ran;//随机生成数1-10放在ran中
while(true){
try {
ran=(int)Math.random()*10+1;//产生1-10的随机数
queue.take();
System.out.println("从队列取走一个元素,队列剩余"+queue.size()+"个元素");
sleep(ran);//线程休眠产生的随机时间ran
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
produce();
}
private void produce() {
int ran;
while(true){
try {
ran=(int)Math.random()*10+1;
queue.put(1);
System.out.println("向队列插入一个元素,队列剩余空间:"+(queueSize-queue.size()));
sleep(ran);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行一下会发现不会出现程序挂起不运行的情况,这证实了阻塞队列是线程安全的,即不会出现生产者阻塞同时消费者阻塞的情况。