我其實不擅長寫線程,主要也是練得少,所以這次在看到層層嵌套的線程寫法時,真是一臉懵逼。
越來越認(rèn)識到讀源碼真是一個令人頭大的過程,非要耐下性子一層層往下探才能了解到設(shè)計者的初衷,但是一旦你讀到最底層,明白了整個邏輯,這種爽快感也是無可比擬的。
套用一位好友的話:
所有的代碼邏輯都是可以放在腦子里跑的,這便是確定性的契機(jī),代碼的世界里還沒有什么靈異可言,只要冷靜分析,總能尋到問題的根源。
所以即使是在做一件未知的事情,也有把握可以在有限的時間內(nèi)達(dá)成。
這也是我耐下性子的原因,因為學(xué)得到東西,因為看得到未來,所以也就有了動力。
共勉。
——————————————
繼上一篇的連接連接之后(見http://www.lxweimin.com/p/d41f32ca22a5),客戶端的的下一步操作一般是
ActiveMQSession session = (ActiveMQSession) connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
// 消息的目的地,消息發(fā)送到那個隊列
Destination destination = session.createQueue(QUEUE_NAME);
// 創(chuàng)建消息發(fā)送者
MessageProducer producer =session.createProducer(destination);
// 設(shè)置是否持久化
TextMessagemessage = session.createTextMessage(msg);
// 發(fā)送消息到目的地方
producer.send(message);
在JMS協(xié)議中,所有的發(fā)送和消費的操作都是在session(會話)中完成的,一個連接中可以包含多個session,那么session到底是什么?
今天主要來看一下session的建立過程,session的一切都從createSession這個方法開始:
connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
在ActiveMQConnection類中找到createSession的方法:
1. 確認(rèn)連接是否正常
2. 確認(rèn)客戶端的連接信息是否被關(guān)系
3. 校驗事務(wù)型session的ack模式是否正確
4. 校驗ack模式是否在范圍內(nèi)
5. 用ActiveMQSession的構(gòu)造函數(shù)構(gòu)造出一個ActiveMQSession
public Session createSession(boolean transacted, int acknowledgeMode) throws JMSException {
//檢查連接有沒關(guān)閉
checkClosedOrFailed();
//確認(rèn)ConnectionInfo已經(jīng)被發(fā)送
ensureConnectionInfoSent();
if(!transacted)
{
//如果transacted為非事務(wù),而acknowledgeMode為事務(wù)SESSION_TRANSACTED,拋出異常
if(acknowledgeMode == 0)
throw new JMSException("acknowledgeMode SESSION_TRANSACTED cannot be used for an non-transacted Session");
//acknowledgeMode不在0-3范圍之內(nèi),這
if(acknowledgeMode < 0 || acknowledgeMode > 4)
throw new JMSException((new StringBuilder()).append("invalid acknowledgeMode: ").append(acknowledgeMode).append(". Valid values are Session.AUTO_ACKNOWLEDGE (1), ").append("Session.CLIENT_ACKNOWLEDGE (2), Session.DUPS_OK_ACKNOWLEDGE (3), ActiveMQSession.INDIVIDUAL_ACKNOWLEDGE (4) or for transacted sessions Session.SESSION_TRANSACTED (0)").toString());
}
return new ActiveMQSession(this, getNextSessionId(), transacted ? 0 : acknowledgeMode != 0 ? acknowledgeMode : 1, isDispatchAsync(), isAlwaysSessionAsync());
}
構(gòu)造函數(shù)里做了這些事情:
1. 會話參數(shù)配置
2. 執(zhí)行器的建立
3. 加入Connection的session隊列并啟動會話
protected ActiveMQSession(ActiveMQConnection connection, SessionId sessionId, int acknowledgeMode, boolean asyncDispatch, boolean sessionAsyncDispatch) throws JMSException {
//一般性參數(shù)設(shè)置,包括開啟DEBUG,連接,Ack模式,Async模式
this.debug = LOG.isDebugEnabled();
this.connection = connection;
this.acknowledgementMode = acknowledgeMode;
this.asyncDispatch = asyncDispatch;
this.sessionAsyncDispatch = sessionAsyncDispatch;
//從ConnectionInfo中構(gòu)造出SessionInfo
this.info = new SessionInfo(connection.getConnectionInfo(), sessionId.getValue());
//事物上下文
setTransactionContext(new TransactionContext(connection));
stats = new JMSSessionStatsImpl(producers, consumers);
//異步發(fā)送消息設(shè)置
this.connection.asyncSendPacket(info);
setTransformer(connection.getTransformer());
//BlobMessage的傳輸設(shè)置
setBlobTransferPolicy(connection.getBlobTransferPolicy());
//連接執(zhí)行器
this.connectionExecutor=connection.getExecutor();
//會話執(zhí)行器
this.executor = new ActiveMQSessionExecutor(this);
//在連接中加入這個Session
connection.addSession(this);
//啟動這個Session(其實就是上一篇中講過的啟動這個session中所有的消費者)
if (connection.isStarted()) {
start();
}
}
問題:可以看到方法里先進(jìn)行connection.isStarted()的判斷才啟動線程呢,這其實就是如果不調(diào)用connection.start()方法,就無法消費的原因。但是為什么要這么設(shè)計?
可以看到最重要的是最后的start()方法,再來回顧一下:
1. Consumer的啟動
2. executor的啟動
/**
* Start this Session.
*
* @throws JMSException
*/
protected void start() throws JMSException {
started.set(true);
for (Iterator<ActiveMQMessageConsumer> iter = consumers.iterator(); iter.hasNext();) {
ActiveMQMessageConsumer c = iter.next();
c.start();
}
executor.start();
}
Consumer的啟動我們放在后續(xù)再看,主要是exectuor,在構(gòu)造函數(shù)里可以看到session的executor就是ActiveMQSessionExecutor的實例,所以又跳到了ActiveMQSessionExecutor這個類里:
1. 構(gòu)造函數(shù)里判斷是否支持消費優(yōu)先級,如果支持就新建SimplePriorityMessageDispatchChannel,否則新建FifoMessageDispatchChannel
2. start()方法中判斷如果MessageQueue沒啟動,就啟動messageQueue,如果發(fā)現(xiàn)messageQueue中存在未消費的消息,就wakeup
3. wakeup中喚醒一個taskRunner。
//構(gòu)造函數(shù)
ActiveMQSessionExecutor(ActiveMQSession session) {
this.session = session;
if (this.session.connection != null && this.session.connection.isMessagePrioritySupported()) {
this.messageQueue = new SimplePriorityMessageDispatchChannel();
}else {
this.messageQueue = new FifoMessageDispatchChannel();
}
}
//executor的啟動
synchronized void start() {
if (!messageQueue.isRunning()) {
messageQueue.start();
if (hasUncomsumedMessages()) {
wakeup();
}
}
}
//判斷是否有未消費的消息
public boolean hasUncomsumedMessages() {
return !messageQueue.isClosed() && messageQueue.isRunning() && !messageQueue.isEmpty();
}
//喚醒
public void wakeup() {
if (!dispatchedBySessionPool) {
if (session.isSessionAsyncDispatch()) {
try {
TaskRunner taskRunner = this.taskRunner;
if (taskRunner == null) {
synchronized (this) {
if (this.taskRunner == null) {
if (!isRunning()) {
// stop has been called
return;
}
this.taskRunner = session.connection.getSessionTaskRunner().createTaskRunner(this,
"ActiveMQ Session: " + session.getSessionId());
}
taskRunner = this.taskRunner;
}
}
taskRunner.wakeup();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
} else {
while (iterate()) {
}
}
}
}
看完有個疑惑,messageQueue是什么?messageQueue的判斷貫穿了整個session的建立過程,在構(gòu)造函數(shù)里可以看到messageQueue是FifoMessageDispatchChannel或SimplePriorityMessageDispatchChannel的一個實例,我們先不考慮支持優(yōu)先級的情況,看看先入先出(FIFO)的messageQueue是什么。
public class FifoMessageDispatchChannel implements MessageDispatchChannel {
//鎖
private final Object mutex = new Object();
//存放消息的鏈表
private final LinkedList<MessageDispatch> list;
private boolean closed;
private boolean running;
public FifoMessageDispatchChannel() {
this.list = new LinkedList<MessageDispatch>();
}
}
MessageDispatch其實可以看做是Message的一層封裝。所以messageQueue其實真的是字面意思,先入先出的消息隊列。
public class MessageDispatch extends BaseCommand {
public static final byte DATA_STRUCTURE_TYPE = CommandTypes.MESSAGE_DISPATCH;
protected ConsumerId consumerId;
protected ActiveMQDestination destination;
protected Message message;
protected int redeliveryCounter;
protected transient long deliverySequenceId;
protected transient Object consumer;
protected transient TransmitCallback transmitCallback;
protected transient Throwable rollbackCause;
看完messageQueue,我們可以想到在初始化的時候,Session中的這個messageQueue其實是沒有消息的,而且從源碼讀來,大部分的功能都取決于messageQueue是否是空(wakeup())。所以我們可以其實可以看到session的主要功能應(yīng)該是針對消費者的。在有未消費的消息的時候,在wakeup中新建了一個taskRunner,這個對象是從connection中建立的,來看一下這個方法:
public TaskRunnerFactory getSessionTaskRunner() {
synchronized (this) {
if (sessionTaskRunner == null) {
sessionTaskRunner = new TaskRunnerFactory("ActiveMQ Session Task", ThreadPriorities.INBOUND_CLIENT_SESSION, false, 1000, isUseDedicatedTaskRunner(), maxThreadPoolSize);
sessionTaskRunner.setRejectedTaskHandler(rejectedTaskHandler);
}
}
return sessionTaskRunner;
}
TaskRunnerFactory工廠類嘛,看看是如何建立TaskRunner的:
public TaskRunner createTaskRunner(Task task, String name) {
init();
ExecutorService executor = executorRef.get();
if (executor != null) {
return new PooledTaskRunner(executor, task, maxIterationsPerRun);
} else {
return new DedicatedTaskRunner(task, name, priority, daemon);
}
}
public void init() {
if (!initDone.get()) {
// If your OS/JVM combination has a good thread model, you may want to
// avoid using a thread pool to run tasks and use a DedicatedTaskRunner instead.
//翻譯:如果你的操作系統(tǒng)或JVM支持一個優(yōu)秀的線程模型,你可能不希望使用thread pool,而是使用DedicatedTaskRunner。
synchronized(this) {
//need to recheck if initDone is true under the lock
//判斷是否需要使用dedicatedTaskRunner
if (!initDone.get()) {
if (dedicatedTaskRunner || "true".equalsIgnoreCase(System.getProperty("org.apache.activemq.UseDedicatedTaskRunner"))) {
executorRef.set(null);
} else {
executorRef.compareAndSet(null, createDefaultExecutor());
}
LOG.debug("Initialized TaskRunnerFactory[{}] using ExecutorService: {}", name, executorRef.get());
initDone.set(true);
}
}
}
}
createTaskRunner整個邏輯是這樣的:
1. 初始化,判斷是否使用DedicatedTaskRunner,如果不使用,就建立一個DefaultExecutor,此時executorRef不為空。如果使用,則把executorRef置為空。
2. 嘗試從executorRef中取出一個線程,如果可以取出來(表示使用了DefaultExecutor),使用PooledTaskRunner來豐富這個線程。如果取不出來,則新建一個DedicatedTaskRunner。
可以看到createTaskRunner里出現(xiàn)了兩種類型:DedicatedTaskRunner和DefaultExecutor。研究了一下兩種類型的代碼,大致可以總結(jié)如下:
- DefaultExecutor是建立了一個JVM線程池ThreadPoolExecutor
- DedicatedTaskRunner是一個單獨的線程,我認(rèn)為應(yīng)該是依靠操作系統(tǒng)或者JVM的線程管理來實現(xiàn)多個線程的并發(fā)
我直接看了看DedicatedTaskRunner的代碼,這個類實例化出的對象里才建立了線程,代碼不多,我覺得這個類的寫法很值得學(xué)習(xí)一下。
class DedicatedTaskRunner implements TaskRunner {
private static final Logger LOG = LoggerFactory.getLogger(DedicatedTaskRunner.class);
private final Task task;
private final Thread thread;
private final Object mutex = new Object();
private boolean threadTerminated;
private boolean pending;
private boolean shutdown;
public DedicatedTaskRunner(final Task task, String name, int priority, boolean daemon) {
// 這個task其實是實例化ActiveMQSessionExecutor的對象
this.task = task;
// 建了個線程,線程里調(diào)用runTask()方法
thread = new Thread(name) {
@Override
public void run() {
try {
runTask();
} finally {
LOG.trace("Run task done: {}", task);
}
}
};
thread.setDaemon(daemon);
thread.setName(name);
thread.setPriority(priority);
thread.start();
}
@Override
public void wakeup() throws InterruptedException {
synchronized (mutex) {
if (shutdown) {
return;
}
pending = true;
mutex.notifyAll();
}
}
/**
* shut down the task
*
* @param timeout
* @throws InterruptedException
*/
@Override
public void shutdown(long timeout) throws InterruptedException {
LOG.trace("Shutdown timeout: {} task: {}", timeout, task);
synchronized (mutex) {
shutdown = true;
pending = true;
mutex.notifyAll();
// Wait till the thread stops ( no need to wait if shutdown
// is called from thread that is shutting down)
if (Thread.currentThread() != thread && !threadTerminated) {
mutex.wait(timeout);
}
}
}
/**
* shut down the task
*
* @throws InterruptedException
*/
@Override
public void shutdown() throws InterruptedException {
shutdown(0);
}
// 執(zhí)行任務(wù)的主方法
final void runTask() {
try {
// 用一個死循環(huán)來不停執(zhí)行。
while (true) {
// 獲取mutex鎖,設(shè)置線程為非掛起狀態(tài)并判斷是否線程關(guān)閉,若關(guān)閉則返回
synchronized (mutex) {
pending = false;
if (shutdown) {
return;
}
}
LOG.trace("Running task {}", task);
// 調(diào)用ActiveMQSessionExecutor的iterate()方法,見下文
if (!task.iterate()) {
// 獲取mutex鎖,判斷線程是否關(guān)閉,如果沒關(guān)閉,則判斷線程是否為非掛起狀態(tài),若是則釋放CPU,等待線程被喚醒。
synchronized (mutex) {
if (shutdown) {
return;
}
while (!pending) {
mutex.wait();
}
}
}
}
} catch (InterruptedException e) {
// Someone really wants this thread to die off.
Thread.currentThread().interrupt();
} finally {
// Make sure we notify any waiting threads that thread
// has terminated.
synchronized (mutex) {
threadTerminated = true;
mutex.notifyAll();
}
}
}
}
學(xué)習(xí)一下,下次寫線程也可以用這種模式。
其實不管是建立了哪種線程,線程的執(zhí)行方法都是調(diào)用ActiveMQSessionExecutor類中的iterate()方法:
public boolean iterate() {
// Deliver any messages queued on the consumer to their listeners.
// 把排隊在消費者上的消息發(fā)送到消費者的監(jiān)聽器上
for (ActiveMQMessageConsumer consumer : this.session.consumers) {
if (consumer.iterate()) {
return true;
}
}
// No messages left queued on the listeners.. so now dispatch messages queued on the session
// 如果沒有消息排隊在監(jiān)聽器上,則把排隊在session中的消息發(fā)送給消費者
MessageDispatch message = messageQueue.dequeueNoWait();
if (message == null) {
return false;
} else {
dispatch(message);
return !messageQueue.isEmpty();
}
}
//消息分發(fā)的方法
void dispatch(MessageDispatch message) {
// TODO - we should use a Map for this indexed by consumerId
// 這個方法里對這個session下所有的consumer進(jìn)行一次循環(huán),根據(jù)消息的consumerID來判斷消息該往哪發(fā)。
// 在一般的使用場景下,一個session只會用一個consumer,所以看起來沒什么問題,但是這個邏輯仍然是低效的。
// 可以看到TODO里表示應(yīng)該用一個MAP來直接找到consumer。
for (ActiveMQMessageConsumer consumer : this.session.consumers) {
ConsumerId consumerId = message.getConsumerId();
if (consumerId.equals(consumer.getConsumerId())) {
consumer.dispatch(message);
break;
}
}
}
總結(jié)
會話的創(chuàng)建最主要的工作是:
1. 配置會話中的參數(shù),根據(jù)連接是否start來判斷是否啟動所有消費者,啟動會話執(zhí)行器ActiveMQSessionExecutor。
2. 在啟動會話執(zhí)行器時,如果消息分發(fā)通道處于未啟動狀態(tài),則啟動消息分發(fā)通道(FIFO或者SimplePriority),如果有未消費的消息,喚醒消息執(zhí)行器。
3. 喚醒主要做的是是通過任務(wù)執(zhí)行工廠TaskRunnerFactory創(chuàng)建執(zhí)行任務(wù)PooledTaskRunner或DedicatedTaskRunner,兩種TaskRunner都是ActiveMQSessionExecutor的包裝,TaskRunner執(zhí)行就是執(zhí)行ActiveMQSessionExecutor的iterate(),這個過程主要是將消息發(fā)送到消費者的listener上,或通過遍歷消費者,將消息分配給消費者。
簡單概括:
一個ActiveMQConnection可對應(yīng)多個ActiveMQSession
一個ActiveMQSession對應(yīng)一個ActiveMQSessionExecutor
通過ActiveMQSessionExecutor來進(jìn)行消息向消費者的分配