1 介绍
ArrayBlockingQueue是一个基于数组结构实现的FIFO阻塞队列,在构造该阻塞队列时需要指定队列中最大元素的数量(容量)。当队列已满时,若再次进行数据写入操作,则线程将会进入阻塞,一直等待直到其他线程对元素进行消费。当队列为空时,对该队列的消费线程将会进入阻塞,直到有其他线程写入数据。该阻塞队列中提供了不同形式的读写方法。
2 API介绍
2.1 阻塞式写方法
/**
向队列的尾部插入新的数据,当队列已满时调用该方法的线程会进入阻塞,
直到有其他线程对该线程执行了中断操作,或者队列中的元素被其他线程消费。
**/
void put(E e) throws InterruptedException;
/**
向队列尾部写入新的数据,
当队列已满时执行该方法的线程在指定的时间单位内将进入阻塞,
直到到了指定的超时时间后,或者在此期间有其他线程对队列数据进行了消费。
当然了,对由于执行该方法而进入阻塞的线程执行中断操作也可以使当前线程退出阻塞。
该方法的返回值boolean为true时表示写入数据成功,为false时表示写入数据失败。
**/
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException
eg:
public static void main(String[] args) {
// 定义一个容量为3的ArrayBlockingQueue
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
try {
queue.put("wade");
queue.put("kobe");
queue.put("t-mac");
System.out.println("队列中已满3个数据,接下来再put数据将进入阻塞");
queue.put("harden");
System.out.println("由于阻塞,不会输出");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 定义一个容量为3的ArrayBlockingQueue
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
try {
queue.offer("wade",10, TimeUnit.SECONDS);
queue.offer("kobe",10, TimeUnit.SECONDS);
queue.offer("t-mac",10, TimeUnit.SECONDS);
System.out.println("队列中已满3个数据,接下来再offer数据将进入阻塞");
boolean res = queue.offer("harden", 10, TimeUnit.SECONDS);
System.out.println("10后退出阻塞,输出,添加数据失败; res: " + res);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2.2 非阻塞式写方法
当队列已满时写入数据,如果不想使得当前线程进入阻塞,那么就可以使用非阻塞式的写操作方法。
/**
向队列尾部写入新的数据,
当队列已满时不会进入阻塞,但是该方法会抛出队列已满的异常。
**/
boolean add(E e);
/**
向队列尾部写入新的数据,当队列已满时不会进入阻塞,并且会立即返回false。
**/
boolean offer(E e);
eg:
public static void main(String[] args) {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
boolean res = queue.add("wade");
System.out.println("wade是否添加成功:" + res);
res = queue.add("wade");
System.out.println("wade是否添加成功:" + res);
res = queue.add("t-mac");
System.out.println("t-mac是否添加成功:" + res);
// 写入失败,抛出异常
res = queue.add("harder");
System.out.println("harder是否添加成功:" + res);
}
输出:
wade是否添加成功:true
wade是否添加成功:true
t-mac是否添加成功:true
Exception in thread "main" java.lang.IllegalStateException: Queue full
public static void main(String[] args) {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3);
boolean res = queue.offer("wade");
System.out.println("wade是否添加成功:" + res);
res = queue.offer("wade");
System.out.println("wade是否添加成功:" + res);
res = queue.offer("t-mac");
System.out.println("t-mac是否添加成功:" + res);
// offer不会抛出异常,但是添加失败
res = queue.offer("harder");
System.out.println("harder是否添加成功:" + res);
}
输出:
wade是否添加成功:true
wade是否添加成功:true
t-mac是否添加成功:true
harder是否添加成功:false
2.3 阻塞式读方法
ArrayBlockingQueue中提供了两个阻塞式读方法
/**
从队列头部获取数据,并且该数据会从队列头部移除,
当队列为空时执行take方法的线程将进入阻塞,
直到有其他线程写入新的数据,或者当前线程被执行了中断操作。
**/
E take();
/**
从队列头部获取数据并且该数据会从队列头部移除,
如果队列中没有任何元素时则执行该方法,当前线程会阻塞指定的时间,
直到在此期间有新的数据写入,或者阻塞的当前线程被其他线程中断,当线程由于超时退出阻塞时,返回值为null。
**/
E poll(long timeout, TimeUnit unit)
eg:
public static void main(String[] args) {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(2);
queue.offer("wade");
queue.offer("t-mac");
try {
String wade = queue.take();
String t_mac = queue.take();
System.out.println(wade + " " + t_mac);
// 没有数据了,即将阻塞
queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2.4 非阻塞式读方法
当队列为空时读取数据,如果不想使得当前线程进入阻塞,那么就可以使用非阻塞式的读操作方法
/**
从队列头部获取数据并且该数据会从队列头部移除,
当队列为空时,该方法不会使得当前线程进入阻塞,而是返回null值。
**/
E poll()
/**
它直接从队列头部获取一个数据,但是并不能从队列头部移除数据,
当队列为空时,该方法不会使得当前线程进入阻塞,而是返回null值。
**/
E peek()
3 生产者消费者
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;
/**
* @author wyaoyao
* @date 2021/4/25 14:46
*/
public class ArrayBlockingQueueTest2 {
public static void main(String[] args) {
final ArrayBlockingQueue<String> QUEUE = new ArrayBlockingQueue<>(10);
// 生产者线程
IntStream.rangeClosed(1, 9).boxed().map(i ->
new Thread(() -> {
while (true) {
try {
String data = String.valueOf(System.currentTimeMillis());
QUEUE.put(data);
System.out.println(Thread.currentThread().getName() + " 生产数据 -> " + data);
TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "P-" + i)).forEach(Thread::start);
IntStream.rangeClosed(1, 9).boxed().map(i ->
new Thread(() -> {
while (true) {
try {
String data = QUEUE.take();
System.out.println(Thread.currentThread().getName() + " 消费数据 -> " + data);
TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(5));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "C-" + i)
).forEach(Thread::start);
}
}
并未提供对共享数据queue的线程安全保护措施,甚至没有进行任何临界值的判断与线程的挂起/唤醒动作,这一切都由该阻塞队列内部实现,因此开发者再也无需实现类似的队列,进行不同类型线程的数据交换和通信