介绍Java TransferQueue类
本文我们介绍来自 java.util.concurrent 包的TransferQueue类。简单地说,该队列可以创建生产者和消费者程序并协调消息从生产者传输到消费者。
该实现类似于BlockingQueue类,但其可以实现反压形式传输,即当生产者利用transfer()方法发送消息给消费者时,生产者将一直被阻塞,直到消息被使用为止。
一个生产者,没有消费者
让我们测试TransferQueue类transfer()方法,期望的行为是生产者被阻塞,直到消费者使用take方法从队列中接收消息。
我们首先创建仅一个生产者但没有消费者的程序。首先调用transfer方法的生产者线程将永被阻塞,因为没有任何消费者从队列中取消息。
生产者Producer类看起来像这样:
public class Producer implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(Producer.class);
private final TransferQueue<String> transferQueue;
private final String name;
final Integer numberOfMessagesToProduce;
final AtomicInteger numberOfProducedMessages = new AtomicInteger();
Producer(TransferQueue<String> transferQueue, String name, Integer numberOfMessagesToProduce) {
this.transferQueue = transferQueue;
this.name = name;
this.numberOfMessagesToProduce = numberOfMessagesToProduce;
}
@Override
public void run() {
for (int i = 0; i < numberOfMessagesToProduce; i++) {
try {
LOG.debug("Producer: " + name + " is waiting to transfer...");
boolean added = transferQueue.tryTransfer("A" + i, 4000, TimeUnit.MILLISECONDS);
if (added) {
numberOfProducedMessages.incrementAndGet();
LOG.debug("Producer: " + name + " transferred element: A" + i);
} else {
LOG.debug("can not add an element due to the timeout");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
构造函数传入TransferQueue实例和我们给生产者的命名以及给队列传输的消息数。因为使用tryTransfer() 方法并设定超时时间,等待4秒,如果生产者在给定时间内不能传输消息则返回false,然后继续下一个消息。numberOfProducedMessages 变量决定生产者生产多少消息。
下面我们看消费者类:
public class Consumer implements Runnable {
private static final Logger LOG = LoggerFactory.getLogger(Consumer.class);
private final TransferQueue<String> transferQueue;
private final String name;
final int numberOfMessagesToConsume;
final AtomicInteger numberOfConsumedMessages = new AtomicInteger();
Consumer(TransferQueue<String> transferQueue, String name, int numberOfMessagesToConsume) {
this.transferQueue = transferQueue;
this.name = name;
this.numberOfMessagesToConsume = numberOfMessagesToConsume;
}
@Override
public void run() {
for (int i = 0; i < numberOfMessagesToConsume; i++) {
try {
LOG.debug("Consumer: " + name + " is waiting to take element...");
String element = transferQueue.take();
longProcessing(element);
LOG.debug("Consumer: " + name + " received element: " + element);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void longProcessing(String element) throws InterruptedException {
numberOfConsumedMessages.incrementAndGet();
Thread.sleep(500);
}
}
和生产者类似,但是通过take方法从队列中接收消息,并通过longProcessing()方法模拟一些长时间动作任务,使用numberOfConsumedMessages 变量表示接收消息数量。
现在使用一个生产者测试程序:
@Test
public void whenUseOneProducerAndNoConsumers_thenShouldFailWithTimeout()
throws InterruptedException {
// given
TransferQueue<String> transferQueue = new LinkedTransferQueue<>();
ExecutorService exService = Executors.newFixedThreadPool(2);
Producer producer = new Producer(transferQueue, "1", 3);
// when
exService.execute(producer);
// then
exService.awaitTermination(5000, TimeUnit.MILLISECONDS);
exService.shutdown();
assertEquals(producer.numberOfProducedMessages.intValue(), 0);
}
我们需要发送是哪个元素给队列,但生产者在第一个元素上被阻塞,因为没有消费者从队列中取消息。tryTransfer()方法被阻塞直到消息被消费或超时,然后返回false表明传输失败,接着传输下一个。测试结果如下:
Producer: 1 is waiting to transfer...
can not add an element due to the timeout
Producer: 1 is waiting to transfer...
一个生产者和一个消费者
下面测试有一个生产者和一个消费者情况:
@Test
public void whenUseOneConsumerAndOneProducer_thenShouldProcessAllMessages()
throws InterruptedException {
// given
TransferQueue<String> transferQueue = new LinkedTransferQueue<>();
ExecutorService exService = Executors.newFixedThreadPool(2);
Producer producer = new Producer(transferQueue, "1", 3);
Consumer consumer = new Consumer(transferQueue, "1", 3);
// when
exService.execute(producer);
exService.execute(consumer);
// then
exService.awaitTermination(5000, TimeUnit.MILLISECONDS);
exService.shutdown();
assertEquals(producer.numberOfProducedMessages.intValue(), 3);
assertEquals(consumer.numberOfConsumedMessages.intValue(), 3);
}
TransferQueue 用作交换点,除非消费者从队列中消费元素,否则生产者不能给队列传输元素,测试结果如下:
Producer: 1 is waiting to transfer...
Consumer: 1 is waiting to take element...
Producer: 1 transferred element: A0
Producer: 1 is waiting to transfer...
Consumer: 1 received element: A0
Consumer: 1 is waiting to take element...
Producer: 1 transferred element: A1
Producer: 1 is waiting to transfer...
Consumer: 1 received element: A1
Consumer: 1 is waiting to take element...
Producer: 1 transferred element: A2
Consumer: 1 received element: A2
因为TransferQueue的特性,生产者和消费者有序协调进行工作。
多个生产者和多个消费者
最后我们测试多个生产者和消费者情况:
@Test
public void whenMultipleConsumersAndProducers_thenProcessAllMessages()
throws InterruptedException {
// given
TransferQueue<String> transferQueue = new LinkedTransferQueue<>();
ExecutorService exService = Executors.newFixedThreadPool(3);
Producer producer1 = new Producer(transferQueue, "1", 3);
Producer producer2 = new Producer(transferQueue, "2", 3);
Consumer consumer1 = new Consumer(transferQueue, "1", 3);
Consumer consumer2 = new Consumer(transferQueue, "2", 3);
// when
exService.execute(producer1);
exService.execute(producer2);
exService.execute(consumer1);
exService.execute(consumer2);
// then
exService.awaitTermination(10_000, TimeUnit.MILLISECONDS);
exService.shutdown();
assertEquals(producer1.numberOfProducedMessages.intValue(), 3);
assertEquals(producer2.numberOfProducedMessages.intValue(), 3);
}
我们设置两个生产者和两个消费者。当程序启动时,两个生产者都产生一个元素之后都被阻塞,直到其中一个消费者从队列中取走元素:
Producer: 1 is waiting to transfer...
Consumer: 1 is waiting to take element...
Producer: 2 is waiting to transfer...
Producer: 1 transferred element: A0
Producer: 1 is waiting to transfer...
Consumer: 1 received element: A0
Consumer: 1 is waiting to take element...
Producer: 2 transferred element: A0
Producer: 2 is waiting to transfer...
Consumer: 1 received element: A0
Consumer: 1 is waiting to take element...
Producer: 1 transferred element: A1
Producer: 1 is waiting to transfer...
Consumer: 1 received element: A1
Consumer: 2 is waiting to take element...
Producer: 2 transferred element: A1
Producer: 2 is waiting to transfer...
Consumer: 2 received element: A1
Consumer: 2 is waiting to take element...
Producer: 1 transferred element: A2
Consumer: 2 received element: A2
Consumer: 2 is waiting to take element...
Producer: 2 transferred element: A2
Consumer: 2 received element: A2
总结
本文我们学习了 java.util.concurrent package包中的TransferQueue 数据结构。通过其构造生产者和消费者程序,除非消费者从队列中消费消息,否则生产者不能发送另一个消息。
TransferQueue队列实现不想让生产者过剩产生大量消息场景非常有用,避免产生OutOfMemory 错误。这样即有消费者决定生产者的速度。