ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序。
LinkedBlockingQueue:是一个基于链表结构的阻塞队列,此队列按FIFO(先进先出)排序元素,吞吐量通常高于ArrayBlockingQueue。
SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于SynchronousQueue。
阻塞队列的好处
在多线程领域:所谓阻塞,在某些情况下会挂起线程,一旦条件满足,被挂起的线程又会被自动唤醒。
为什么需要BlockingQueue
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为一切BlockingQueue都给你一手包办了。
在Concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,这会给我们的程序带来不小的复杂度。
阻塞队列的核心方法
方法类型 | 抛出异常 | 特殊值 | 阻塞 | 超时 |
插入 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除 | remove() | poll() | take() | poll(time,uni) |
检查 | element() | peek() | 不可用 | 不可用 |
抛出异常 | 当阻塞队列满时,再往队列里add插入元素会抛出IllegalStateException: Queue full 当阻塞队列空时,再往队列里remove移除元素会抛出NoSuchElementException 当阻塞队列为空时,使用element判断第一个元素时抛出NoSuchElementException |
特殊值 | offer(),插入方法,成功true失败false poll(),移除方法,成功返回出队列的元素,队列中没有元素就返回null |
一直阻塞 | 当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞,直到put或响应中断退出 当阻塞队列为空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程,直到队列可用 |
超时退出 | 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过时间后线程会退出 |
队列满时add
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(1);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
队列空时remove
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(1);
//先进先出,清除最先进入的元素
blockingQueue.remove();
队列满时offer
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(1);
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
队列空时poll
BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(1);
System.out.println(blockingQueue.poll());
队列满时put
static BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(1);
创建两个线程分别为生产者和消费者
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("开始");
blockingQueue.put("a");
blockingQueue.put("b");
System.out.println("结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
},"生产者").start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10_000);
blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
},"消费者").start();
上面的例子中,我们可以看到的现象是生产者线程打印”开始“到打印”结束“之间经历了10秒。
这是因为当生产者线程向队列中put一个b元素时,由于队列已满,因此发生了阻塞,
此时,进过10秒的休眠的消费者线程从线程中取出a元素,队列未满,生产者线程被唤醒,将b放入队列,程序结束。
SynchronousQueue队列
SynchronousQueue没有容量。
与其他BlockingQueue不同,SynchronousQueue是一个不存储元素的BlockingQueue。
每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。
static BlockingQueue<String> blockingQueue=new SynchronousQueue();
new Thread(new Runnable() {
@Override
public void run() {
try {
blockingQueue.put("a");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
},"生产者").start();
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println(blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
};
},"消费者").start();
只执行put或者只执行take方法,线程都会处于阻塞状态,因此put操作必须配合take操作。