當我們想通過多條線程處理activemq中的消息,直覺上會使用固定大小線程池去處理,然而這種方式並不妥當,這麼做我們只是將消息從activemq轉移到線程池的阻塞隊列之中,當線程池開始工作,activemq中的消息快速被消費完畢,而消息所代表的任務卻並未真正被處理, 他們被堆積在處理程序的內存中,並陸續由線程中的線程處理。這會產生副作用,此時當處理程序因爲某種原因而崩潰,這些待處理的任務都將丟失。
如何實現既能通過多個線程處理任務,又能保證未完成的任務的安全性,此時 SynchronousQueue 就有了用武之地。
我們可以把SynchronousQueue 當作長度爲1的阻塞隊列,當隊列被塞入一個元素,假如這個元素未被消費掉,那麼後續的塞入操作將被阻塞。我們可以利用它的這個特性,把它當作是activemq與處理線程之間的緩衝層。在 SynchronousQueue 的一端,我們從activemq中讀取一個元素,並將它put進SynchronousQueue 。在另一端,多條線程分別從 SynchronousQueue 中 take 元素進行處理,只有當 SynchronousQueue 中不存在任何元素,也就是線程們將當前的任務都處理完畢,還有一端的從activemq中提取消息的操作才能執行,反之則將被阻塞。 通過這種方式,我們便能保證任務不丟失的同時又能通過多線程處理它們。示例代碼如下
初始化一個 SynchronousQueue
private SynchronousQueue<ActiveMQObjectMessage> synchronousQueue = new SynchronousQueue<>();
從activemq中將消息轉移至synchronousQueue,一次轉移一條,如果上一條未被處理,下一條不能繼續
ConnectionFactory factory = new ActiveMQConnectionFactory(ActiveMQConnection.DEFAULT_USER, ActiveMQConnection
.DEFAULT_PASSWORD, brokerUrl);
connection = factory.createConnection();
connection.start();
session = connection.createSession(Boolean.FALSE, Session.CLIENT_ACKNOWLEDGE);
Destination destination = session.createQueue(dest);
MessageConsumer consumer = session.createConsumer(destination);
while (true) {
try {
Message message = consumer.receive();
if (message instanceof ActiveMQObjectMessage) {
ActiveMQObjectMessage activeMQObjectMessage = (ActiveMQObjectMessage) message;
synchronousQueue.put(activeMQObjectMessage);
} else {
if (message != null) {
message.acknowledge();
logger.error("消息格式錯誤,msg={}",message.toString());
}
}
} catch (JMSException | InterruptedException e) {
e.printStackTrace();
}
}
開啓多條線程同時處理消息
Runnable task = () -> {
while (true) {
try {
ActiveMQObjectMessage activeMQObjectMessage = synchronousQueue.take();
//消費消息,處理成功後確認
boolean complete = handle(msg);
if (complete) {
activeMQObjectMessage.acknowledge();
}
} catch ( JMSException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < threads; i++) {
Thread thread = new Thread(task);
thread.setName("log-task-" + i);
thread.start();
}