阻塞队列
1.什么是阻塞队列?
- 阻塞队列首先是一个队列,当队列是空的时候,从队列获取元素的操作将会被阻塞,当队列是满的时候,从队列插入元素的操作将会被阻塞。
线程1往队阻塞列里添加元素,线程2从阻塞队列中移除元素。
试图从空队列里移除元素的线程2(包括其他线程)会被阻塞,直到线程1(或其他线程)往队列里新增元素。
试图往已满的阻塞队列里新增元素的线程1(包括其他线程)会被阻塞,直到线程2(或其他线程)移除一个(或多个)是阻塞队列变得空闲才能继续新增元素。
至于何时挂起线程和唤醒线程,我们不需要关心,BlockingQueue帮我们做好了。
2.阻塞队列分类(加粗的为常用)
(1)ArrayBlockingQueue:由数组结构组成的有界阻塞队列。(此队列按照先进先出(FIFO)的原则对元素进行排序。支持公平锁和非公平锁。默认是非公平)
(2)LinkedBlockingQueue:由链表结果组成的有界阻塞队列(默认大小Integer.MAX_VALUE)阻塞队列。按照先进先出的顺序进行排序。
(3)SychronousQueue:不存储元素的阻塞队列,也即单个元素队列。(默认是非公平锁)
(4)PriorityBlockingQueue:支持优先级排序的无界阻塞队列,默认情况下元素采用自然顺序升序排列。也可以自定义类实现compareTo()方法来指定元素排序规则(默认的容量是11,数据结构是最小二叉堆)。
(5)DelayQueue:使用优先级队列实现的延迟无界阻塞队列。用于放置实现了Delayed接口的对象,其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。注意:不能将null元素放置到这种队列中。
(6)LinkedTransferQueue:由链表结构组成的无界阻塞队列。
(7)LinkedBlockingDeque:由链表结构组成的双端阻塞队列。即可以从队列的两端插入和移除元素。(只有这个没有锁)
3.BlockingQueue的核心方法
方法\处理方式 | 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 |
---|---|---|---|---|
插入方法 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除方法 | remove() | poll() | take() | poll(time,unit) |
检查方法 | element() | peek() | 不可用 | 不可用 |
3.1抛出异常(指在阻塞对列满时,继续add元素会抛出 java.lang.IllegalStateException异常,对列为空时,继续remove元素会抛java.util.NoSuchElementException异常,element()检查对列,并返回队首元素,如果是是空队列则抛出java.util.NoSuchElementException异常)
以ArrayBlockingQueue为例:
package com.sk.Multithreading;
import java.util.concurrent.*;
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add(1));
System.out.println(blockingQueue.add(1));
System.out.println(blockingQueue.add(1));
System.out.println(blockingQueue.add(1));
}
}
package com.sk.Multithreading;
import java.util.concurrent.*;
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.add(1));
System.out.println(blockingQueue.add(1));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
}
}
add方法底层是去调用offer方法,根据offer方法的返回值(true,false)来决定是否抛出异常
remove方法底层调用poll方法,根据返回值是未null,来决定是否抛出异常
element()方法底层是peek()方法,也是根据返回值是否为null,来判定是否抛出异常
3.2返回特殊值(插入成功返回true失败返回false,队列为空时,移除元素返回null)
以ArrayBlockingQueue为例
package com.sk.Multithreading;
import java.util.concurrent.*;
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(blockingQueue.offer(1));
System.out.println(blockingQueue.offer(1));
System.out.println(blockingQueue.offer(1));
System.out.println(blockingQueue.offer(1));
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
}
}
运行结果
3.3一直阻塞(当队列满时,继续往队列put元素的线程会被阻塞,直到能put进去;当队列为空时,从队列取元素的线程会被阻塞,直到队列不为空,方法没有返回值)
package com.sk.Multithreading;
import java.util.concurrent.*;
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.put(1);
blockingQueue.put(1);
blockingQueue.put(1);
System.out.println("醉后不知天在水,满船清梦压星河。");
blockingQueue.put(1);
}
}
可以看出打印出诗句后,程序并没有结束,这是因为线程被挂起,还有一个元素没有put进去。
3.4超时(设置一定时间,超出这个时间,线程会退出)
package com.sk.Multithreading;
import java.util.concurrent.*;
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.offer(1,2,TimeUnit.SECONDS);
blockingQueue.offer(1,2,TimeUnit.SECONDS);
blockingQueue.offer(1,2,TimeUnit.SECONDS);
System.out.println("醉后不知天在水,满船清梦压星河。");
blockingQueue.offer(1,2,TimeUnit.SECONDS);
}
}
结果(此时还未超出2秒的时间,程序未结束)
2秒后程序结束
3.4SynchronousQueue不存储元素,生产一个消费一个。
package com.sk.Multithreading;
import java.util.concurrent.*;
public class BlockingQueueDemo {
public static void main(String[] args) {
BlockingQueue<Integer> blockingQueue = new SynchronousQueue<Integer>();
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + "\t put 1");
blockingQueue.put(1);
System.out.println(Thread.currentThread().getName() + "\t put 2");
blockingQueue.put(2);
System.out.println(Thread.currentThread().getName() + "\t put 3");
blockingQueue.put(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "玉无双").start();
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "\t take " + blockingQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "\t take " + blockingQueue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "\t take " + blockingQueue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "花辞树").start();
}
}
让花辞树线程线程睡眠2秒是方面观察,当队列中put进去一个元素,只有take把元素拿出来后,下一个元素才能put进去。
4.阻塞队列的应用
4.1生产者和消费者模式
package com.sk.Multithreading;
import java.util.concurrent.*;
class DataSource{
BlockingQueue<Integer> queue ;
public DataSource(BlockingQueue<Integer> queue) {
this.queue = queue;
}
}
public class BlockingQueueDemo {
public static void main(String[] args) {
BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(5);
DataSource dataSource = new DataSource(blockingQueue);
new Thread(() -> {
try {
for (int i = 1; i <= 10 ; i++) {
dataSource.queue.put(i);
System.out.println(Thread.currentThread().getName()+i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "玉无双").start();//生产者
new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName()+dataSource.queue.take());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "花辞树").start();//消费者
}
}
4.2.消息中间件底层核心就是阻塞队列
4.3线程池
- SingleThreadExecutor和·FixedThreadPool用的是LinkedBlockingQueue
- newCachedThreadPool用的是SynchronousQueue