ActiveMQ源碼解析(二)會話是什么

我其實不擅長寫線程,主要也是練得少,所以這次在看到層層嵌套的線程寫法時,真是一臉懵逼。
越來越認(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é)如下:

  1. DefaultExecutor是建立了一個JVM線程池ThreadPoolExecutor
  2. 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)行消息向消費者的分配

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內(nèi)容