阻塞队列
概念:当阻塞队列为空时,获取(take)操作是阻塞的;当阻塞队列为满时,添加(put)操作是阻塞的。
好处:阻塞队列不用手动控制什么时候该被阻塞,什么时候该被唤醒,简化了操作。
体系:Collection→Queue→BlockingQueue→七个阻塞队列实现类。
ArrayBlockingQueue:由数组构成的有界阻塞队列LinkedBlockingQueue:由链表构成的有界阻塞队列PriorityBlockingQueue:支持优先级排序的无界阻塞队列DelayQueue:支持优先级的延迟无界阻塞队列SynchronousQueue:单个元素的阻塞队列LinkedTransferQueue:由链表构成的无界阻塞队列LinkedBlockingDeque:由链表构成的双向阻塞队列
粗体标记的三个用得比较多,许多消息中间件底层就是用它们实现的。
需要注意的是LinkedBlockingQueue虽然是有界的,但有个巨坑,其默认大小是Integer.MAX_VALUE,高达21亿,一般情况下内存早爆了(在线程池的ThreadPoolExecutor有体现)。
API:抛出异常是指当队列满时,再次插入会抛出异常;返回布尔是指当队列满时,再次插入会返回false;阻塞是指当队列满时,再次插入会被阻塞,直到队列取出一个元素,才能插入。超时是指当一个时限过后,才会插入或者取出。API使用见BlockingQueueDemo。
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);
//addArdRemove(blockingQueue);
//offerAndPoll(blockingQueue);
//putAndTake(blockingQueue);
outOfTime(blockingQueue);
}
public static void outOfTime(BlockingQueue<String> blockingQueue) throws InterruptedException {
System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
}
public static void putAndTake(BlockingQueue<String> blockingQueue) throws InterruptedException {
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
blockingQueue.put("d");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
}
public static void offerAndPoll(BlockingQueue<String> blockingQueue) throws InterruptedException {
blockingQueue.offer("a");
blockingQueue.offer("b");
blockingQueue.offer("c");
blockingQueue.offer("d");
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
}
public static void addArdRemove(BlockingQueue<String> blockingQueue) {
blockingQueue.add("a");
blockingQueue.add("b");
blockingQueue.add("c");
blockingQueue.add("d");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
}
}
SynchronousQueue
队列只有一个元素,如果想插入多个,必须等队列元素取出后,才能插入,只能有一个“坑位”,用一个插一个,详见
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName() + "\tput 1");
blockingQueue.put("1");
System.out.println(Thread.currentThread().getName() + "\tput 2");
blockingQueue.put("2");
System.out.println(Thread.currentThread().getName() + "\tput 3");
blockingQueue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"AAA").start();
new Thread(()->{
try {
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
} catch (Exception e) {
e.printStackTrace();
}
},"BBB").start();
}
}
Callable接口
与Runnable的区别:
- 1.Callable带返回值。
- 2.会抛出异常。
- 3.覆写
call()方法,而不是run()方法。
Callable接口的使用:
public class CallableDemo {
//实现Callable接口
static class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("callable come in...");
return 1024;
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建FutureTask类,接受MyThread。
FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());
//将FutureTask对象放到Thread类的构造器里面。
new Thread(futureTask, "AA").start();
int result01 = 100;
//用FutureTask的get方法得到返回值。
Integer result02 = futureTask.get();
System.out.println("result=" + (result01 + result02));
}
}
阻塞队列的应用——生产者消费者
传统模式
传统模式使用Lock来进行操作,需要手动加锁、解锁。详见ProdConsTradiDemo。
class ShareData {
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void increment() throws InterruptedException {
//上锁、判断、干活、通知
lock.lock();
try {
while (number != 0) {
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + "\t" + number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() throws InterruptedException {
lock.lock();
try {
while (number == 0) {
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + "\t" + number);
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class ProdConsTradiDemo {
public static void main(String[] args) {
ShareData shareData = new ShareData();
new Thread(()->{
for (int i = 0; i < 50; i++) {
try {
shareData.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"生产者").start();
new Thread(()->{
for (int i = 0; i < 50; i++) {
try {
shareData.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"消费者").start();
}
}
使用阻塞队列就不需要手动加锁了,详见ProdConsBlockQueueDemo。
public class ProdConsBlockQueueDemo {
public static void main(String[] args) {
MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "\t生产线程启动");
try {
myResource.myProd();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"生产者").start();
new Thread(()->{
System.out.println(Thread.currentThread().getName() + "\t消费线程启动");
try {
myResource.myCons();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"消费者").start();
try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("5秒后叫停");
myResource.stop();
}
}
class MyResource {
//使用volatile保证可见性
private volatile boolean FLAG = true; //默认开启,进行生产+消费
private AtomicInteger atomicInteger = new AtomicInteger();
private BlockingQueue<String> blockingQueue;
public MyResource(BlockingQueue<String> blockingQueue) {
this.blockingQueue = blockingQueue;
}
public void myProd() throws InterruptedException {
String data = null;
boolean retValue;
while (FLAG) {
data = atomicInteger.getAndIncrement() + "";
retValue = blockingQueue.offer(data, 2L, TimeUnit.SECONDS);
if (retValue) {
System.out.println(Thread.currentThread().getName() + "\t插入队列" + data + "成功");
} else {
System.out.println(Thread.currentThread().getName() + "\t插入队列" + data + "失败");
}
try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println(Thread.currentThread().getName() + "\tFLAG==false停止生产");
}
public void myCons() throws InterruptedException {
String res;
while (FLAG) {
res = blockingQueue.poll(2L, TimeUnit.SECONDS);
if (null == res || res.equalsIgnoreCase("")) {
FLAG = false;
System.out.println(Thread.currentThread().getName() + "\t超过2秒钟没有消费,退出消费");
return;
}
System.out.println(Thread.currentThread().getName() + "\t消费队列" + res + "成功");
}
}
public void stop() {
this.FLAG = false;
}
}