阻塞队列BlockingQueue
阻塞队列是一种数据结构,它具有线程安全性,可以用于多线程环境中的生产者消费者模式,其中生产者将消息插入队列,消费者将消息从队列中删除并处理。
它是一个抽象接口,提供了几个方法如 put() 和 take(),这些方法在队列为空或队列已满时会阻塞线程,直到队列中有足够的空间或足够的元素可供获取。
BlockingQueue 接口有多种实现,例如 ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue 等。
下面是一个使用 ArrayBlockingQueue 的示例:
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class ProducerConsumerPattern { public static void main(String[] args) throws InterruptedException { BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(10); Thread producerThread = new Thread(() -> { for (int i = 1; i <= 50; i++) { try { queue.put(i); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread consumerThread = new Thread(() -> { for (int i = 1; i <= 50; i++) { try { int data = queue.take(); System.out.println("Consumed: " + data); } catch (InterruptedException e) { e.printStackTrace(); } } }); producerThread.start(); consumerThread.start(); producerThread.join(); consumerThread.join(); } }
非阻塞队列ConcurrentLinkedQueue
非阻塞队列是一种线程安全的队列,它支持高并发的读写操作。ConcurrentLinkedQueue 是一种常见的非阻塞队列,它基于链表实现。
ConcurrentLinkedQueue 不会像 BlockingQueue 一样在队列满或队列为空时阻塞线程,取而代之的是它使用了一些特殊的算法来处理并发操作,因此能够支持高并发。
下面是一个使用 ConcurrentLinkedQueue 的示例:
import java.util.Queue; import java.util.concurrent.ConcurrentLinkedQueue; public class ProducerConsumerPattern { public static void main(String[] args) throws InterruptedException { Queue<Integer> queue = new ConcurrentLinkedQueue<>(); Thread producerThread = new Thread(() -> { for (int i = 1; i <= 50; i++) { queue.offer(i); } }); Thread consumerThread = new Thread(() -> { for (int i = 1; i <= 50; i++) { int data = queue.poll(); System.out.println("Consumed: " + data); } }); producerThread.start(); consumerThread.start(); producerThread.join(); consumerThread.join(); } }
同步队列SyncQueue
同步队列是一种特殊的队列,它可以用于线程间的手递手操作,其中每个线程都必须等待其他线程完成它们的操作,然后才能继续执行。
Java 中的 SynchronousQueue 就是一种同步队列,它可以实现生产者消费者模式。当一个线程试图向队列中添加元素时,它会被阻塞,直到另一个线程从队列中取走元素。同样,当一个线程尝试从队列中获取元素时,它会被阻塞,直到另一个线程向队列中添加元素。
下面是一个使用 SynchronousQueue 的示例:
import java.util.concurrent.SynchronousQueue; public class ProducerConsumerPattern { public static void main(String[] args) throws InterruptedException { SynchronousQueue<Integer> queue = new SynchronousQueue<>(); Thread producerThread = new Thread(() -> { for (int i = 1; i <= 50; i++) { try { queue.put(i); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread consumerThread = new Thread(() -> { for (int i = 1; i <= 50; i++) { try { int data = queue.take(); System.out.println("Consumed: " + data); } catch (InterruptedException e) { e.printStackTrace(); } } }); producerThread.start(); consumerThread.start(); producerThread.join(); consumerThread.join(); } }
小故事
阻塞队列BlockingQueue的小故事:
有个工厂生产零部件,每一个零部件都需要经过两个工人的加工才能完成。第一个工人在生产零部件的时候将其放入一个装满零部件的待处理队列中,第二个工人从待处理队列中拿出零部件加工并放到另一个已完成队列中。为了保证生产线的稳定运作,待处理队列和已完成队列的长度必须有限制。如果待处理队列满了,第一个工人就不能再往里面放零部件了,必须等待第二个工人从队列中取出零部件。这就是阻塞队列的工作原理,当队列满了或者空了,生产者和消费者线程会被阻塞等待,直到队列中有可用的元素或者有空位。
非阻塞队列ConcurrentLinkedQueue的小故事:
有个商店在进行促销活动,每隔一段时间就会发布一个优惠券,顾客可以领取这个优惠券,但是每个顾客只能领取一次。商店把每个领取过优惠券的顾客名字放进了一个队列中,可以使用ConcurrentLinkedQueue作为这个队列的数据结构。当一个顾客想领取优惠券时,它会先检查自己是否已经领取过,如果没有,则将自己的名字放进队列中。这时候,其他想领取优惠券的顾客也会在队列中插入他们的名字,但是由于队列使用了无锁的CAS(Compare and Swap)操作实现并发访问,所以他们之间不会产生争用而导致线程阻塞。
同步队列SyncQueue的小故事:
有个小区住户需要把垃圾袋放到小区的垃圾桶中。小区设置了一个运输车队,每个运输车都可以装载一定数量的垃圾袋。当一个住户需要丢垃圾时,他会把垃圾袋放到自己居住楼层的垃圾桶旁边,然后按下一个按钮,运输车就会来收集这些垃圾袋。当运输车到达住户居住楼层时,它会把垃圾袋从垃圾桶中取出并装进自己的运输车中,当运输车装满时,它就会离开小区去倾倒垃圾。这就是同步队列的工作原理,每个住户都可以把自己的垃圾袋放到队列中,但是只有运输车到达时,才能从队列中取出垃圾袋进行处理。这个过程需要同步管理,以确保不会有两个车同时去同一个地方或者一个车去了两次同一个地方。