在Mina的使用中,線程池的配置一個(gè)比較關(guān)鍵的環(huán)節(jié),同時(shí)它也是Mina性能提高的一個(gè)有效的方法,在Mina的2.0以上版本中已經(jīng)不再需要對(duì)Mina線程池的配置了,本系列文章都是基于當(dāng)前的穩(wěn)定版本Mina 1.1.7版來進(jìn)行講述的,Mina的2.0以上版本現(xiàn)在還都是M(millestone,即里程碑)版的,在1.5版本上2.0M版為穩(wěn)定版本,但是在1.5+以上則為非穩(wěn)定版本,所以,為了更好的進(jìn)行討論和學(xué)習(xí),還是基于Mina 1.1.7版本進(jìn)行討論,如果使用Mina 2.0進(jìn)行開發(fā)要注意JDK的版本問題,當(dāng)然如果有能力的話也可以自行修改和編譯Mina的2.0版本,這里對(duì)此就不再多說,使用2.0版本的同學(xué)可以不用理會(huì)本文的內(nèi)容。
?
?
上面的內(nèi)容都是基于Apache Mina提供的文檔講述,如有需要,請(qǐng)自行查找相關(guān)資料,在此不再贅述。下面開始對(duì)Mina的線程模型的配置、使用、及ExcutorFilter的基本原理進(jìn)行簡(jiǎn)單的講解。
配置Mina的三種工作線程
在Mina的NIO模式中有三種I/O工作線程(這三種線程模型只在NIO Socket中有效,在NIO數(shù)據(jù)包和虛擬管道中沒有,也不需要配置):
Acceptor thread
該線程的作用是接收客戶端的連接,并將客戶端的連接導(dǎo)入到I/O processor線程模型中。所謂的I/O processor線程模型就是Mina的I/O processor thread。Acceptor thread在調(diào)用了Acceptor.bind()方法后啟動(dòng)。每個(gè)Acceptor只能創(chuàng)建一個(gè)Acceptor thread,該線程模型不能配置,它由Mina自身提供。
Connector thread
該線程模型是客戶端的連接線程模型,它的作用和Acceptor thread類似,它將客戶端與服務(wù)器的連接導(dǎo)入到I/O processor線程模型中。同樣地,該線程模型也是由Mina的客戶端自動(dòng)創(chuàng)建,該線程模型也不能進(jìn)行配置。
I/O processor thread
該線程模型的主要作用就行接收和發(fā)送數(shù)據(jù),所有的IO操作在服務(wù)器與客戶端的連接建立后,所有的數(shù)據(jù)的接收和發(fā)送都是有該線程模型來負(fù)責(zé)的,知道客戶端與服務(wù)器的連接關(guān)閉,該線程模型才停止工作。該線程模型可以由程序員根據(jù)需要進(jìn)行配置。該線程模型默認(rèn)的線程的數(shù)量為cpu的核數(shù)+1。若你的cpu為雙核的,則你的I/O processor 線程的最大數(shù)量為3,同理若你的若你的cpu為四核的,那么你的I/O processor 線程的最大數(shù)量為5。
由上面的內(nèi)容我們可以知道在Mina中可以配置的線程數(shù)量只有I/O processor,對(duì)于每個(gè)IoService再創(chuàng)建其實(shí)例的時(shí)候可以配置該IoService的I/O processor的線程數(shù)量。在SokcetConnector和SocketAccpetor中I/O Processor的數(shù)量是由CPU的核數(shù)+1來決定的。
他們的配置方式如下:
?
?
/*** * 配置SocketAcceptor監(jiān)聽器的I/O Processor的線程的數(shù)量, * 此處的I/O Processor的線程數(shù)量由CPU的核數(shù)決定,但Acceptor * 的線程數(shù)量只有一個(gè),也就是接收客戶端連接的線程數(shù)只有一個(gè), * Acceptor的線程數(shù)量不能配置。 * */ SocketAcceptor acceptor = new SocketAcceptor(Runtime.getRuntime() .availableProcessors() + 1, Executors.newCachedThreadPool()); /*** * 配置SocketConnector監(jiān)聽器的I/O Processor的線程的數(shù)量, * 此處的I/O Processor的線程數(shù)量由CPU的核數(shù)決定,但SocketConnector * 的線程數(shù)量只有一個(gè),也就是接收客戶端連接的線程數(shù)只有一個(gè), * SocketConnector的線程數(shù)量不能配置。 * */ SocketConnector connector = new SocketConnector(Runtime.getRuntime() .availableProcessors() + 1, Executors.newCachedThreadPool());
? 在上面的配置比較難以理解的地方就是Runtime.getRuntime().availableProcessors() + 1,它的意思就是由JVM根據(jù)系統(tǒng)的情況(即CPU的核數(shù))來決定IO Processor的線程的數(shù)量。雖然這個(gè)線程的數(shù)量是在SocketAcceptor /SocketConnector 的構(gòu)造器中進(jìn)行的,但是對(duì)于SocketAcceptor /SocketConnector自身的線程沒有影響,SocketAcceptor /SocketConnector的線程數(shù)量仍然為1。為SocketAcceptor /SocketConnector本身就封裝了IO Processor,SocketAcceptor /SocketConnector只是由一個(gè)單獨(dú)的線程來負(fù)責(zé)接收外部連接/向外部請(qǐng)求建立連接,當(dāng)連接建立后,SocketAcceptor /SocketConnector會(huì)把數(shù)據(jù)收發(fā)的任務(wù)轉(zhuǎn)交I/O Processor的線程。這個(gè)在本系列文章的《IoFilter和IoHandler的區(qū)別和聯(lián)系》中的圖示中可以看。
圖中清晰的顯示了IO Processor就是位于IoService和IoFilter之間,IoService負(fù)責(zé)和外部建立連接,而IoFilter則負(fù)責(zé)處理接收到的數(shù)據(jù),IoProcessor則負(fù)責(zé)數(shù)據(jù)的收發(fā)工作。
關(guān)于配置IO Processor的線程數(shù)量還有一種比較“笨”的辦法,那就一個(gè)一個(gè)試,你可以根據(jù)你的PC的硬件情況從1開始,每次加1,然后得出IO Processor的最佳的線程的數(shù)量。但是這種方式個(gè)人建議最好不要用了,上面的方法足矣。配置方法如下:
//從1--N開始嘗試,N的最大數(shù)量為CPU核數(shù)+1 SocketAcceptor acceptor = new SocketAcceptor(N, Executors.newCachedThreadPool());
?
為Mina的IoFilterChain添加線程池
在Mina的API中提供了一個(gè)ExecutorFilter,該線程池實(shí)現(xiàn)了IoFilter接口,它可以作為一個(gè)IoFilter添加到IoFilterChain中,它的作用就是將I/O Processor中的事件通過其自身封裝的一個(gè)線程池來轉(zhuǎn)發(fā)到下一個(gè)過濾器中。在沒有添加該線程模型時(shí),I/O Processor的事件是通過方法來觸發(fā)的,然后轉(zhuǎn)發(fā)給IoHandler。在沒有添加該線程池的時(shí)候,所有的事件都是在單線程模式下運(yùn)行的,也就是說有的事件和處理(IO Processor,IoHandler,IoFilter)都是運(yùn)行在同一個(gè)線程上,這個(gè)線程就是IO Processor的線程,但是這個(gè)線程的數(shù)量受到CPU核數(shù)的影響,因此系統(tǒng)的性能也直接受CPU核數(shù)的影響。
比較復(fù)雜的應(yīng)用一般都會(huì)用到該線程池,你可以根據(jù)你的需求在IoFilterchain中你可以添加任意數(shù)量的線程池,這些線程池可以組合成一個(gè)事件驅(qū)動(dòng)(SEDA)的處理模型。對(duì)于一般的應(yīng)用來說不是線程的數(shù)量越多越好,線程的數(shù)量越多可能會(huì)加劇CPU切換線程所耗費(fèi)的時(shí)間,反而會(huì)影響系統(tǒng)的性能,因此,線程的數(shù)量需要根據(jù)實(shí)際的需要由小到大,逐步添加,知道找到適合你系統(tǒng)的最佳線程的數(shù)量。ExcutorFilter的配置過程如下:
SocketAcceptor acceptor = ...; DefaultIoFilterChainBuilder filterChainBuilder = acceptor.getDefaultConfig().getFilterChain(); filterChainBuilder.addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool());
?在配置該線程池的時(shí)候需要注意的一個(gè)問題是,當(dāng)你使用自定的ProtocolCodecFactory時(shí)候一定要將線程池配置在該過濾器之后,如下所示:
DefaultIoFilterChainBuilder filterChainBuilder = acceptor.getDefaultConfig().getFilterChain(); // 和CPU綁定的操作配置在過濾器的前面 filterChainBuilder.addLast("codec", new ProtocolCodecFactory(...)); // 添加線程池 filterChainBuilder.addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool());
?因?yàn)槟阕约簩?shí)現(xiàn)的ProtocolCodecFactory直接讀取和轉(zhuǎn)換的是二進(jìn)制數(shù)據(jù),這些數(shù)據(jù)都是由和CPU綁定的I/O Processor來讀取和發(fā)送的,因此為了不影響系統(tǒng)的性能,也應(yīng)該將數(shù)據(jù)的編解碼操作綁定到I/O Processor線程中,因?yàn)樵贘ava中創(chuàng)建和線程切換都是比較耗資源的,因此建議將ProtocolCodecFactory配置在ExecutorFilter的前面。關(guān)于ProtocolCodecFactory詳細(xì)講述會(huì)在后續(xù)的文檔中給出,此處就不多說了。
最后給出一個(gè)服務(wù)器線程模型完整配置的例子,該例子和KFCClient一起配置使用,詳細(xì)代碼在附件中,此處只給出代碼的主要部分:???????
SocketAddress address = new InetSocketAddress("localhost", 4321); /*** * 配置SocketAcceptor監(jiān)聽器的I/O Processor的線程的數(shù)量, 此處的I/O * Processor的線程數(shù)量由CPU的核數(shù)決定,但Acceptor 的線程數(shù)量只有一個(gè),也就是接收客戶端連接的線程數(shù)只有一個(gè), * Acceptor的線程數(shù)量不能配置。 * */ IoAcceptor acceptor = new SocketAcceptor(Runtime.getRuntime() .availableProcessors() + 1, Executors.newCachedThreadPool()); acceptor.getDefaultConfig().setThreadModel(ThreadModel.MANUAL); // 配置數(shù)據(jù)的編解碼器 acceptor.getDefaultConfig().getFilterChain().addLast("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory())); // 此處為你自己實(shí)現(xiàn)的編解碼器 // config.getFilterChain().addLast("codec", new // ProtocolCodecFactory(...)); // 為IoFilterChain添加線程池 acceptor.getDefaultConfig().getFilterChain().addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool())); acceptor.getDefaultConfig().getFilterChain().addLast("logger", new LoggingFilter()); // 綁定服務(wù)器端口 acceptor.bind(address, new KFCFoodPriceHandler()); System.out.println(" 服務(wù)器開始在 8000 端口監(jiān)聽 ......."); // ==========================================// // 此處為客戶端的I/O Processor線程數(shù)的配置,你可以模仿 // // IoAcceptor配置來實(shí)現(xiàn) // // ==========================================// /*** * 配置SocketConnector監(jiān)聽器的I/O Processor的線程的數(shù)量, 此處的I/O * Processor的線程數(shù)量由CPU的核數(shù)決定,但SocketConnector * 的線程數(shù)量只有一個(gè),也就是接收客戶端連接的線程數(shù)只有一個(gè), SocketConnector的線程數(shù)量不能配置。 * */ // SocketConnector connector = new SocketConnector(Runtime.getRuntime() // .availableProcessors() + 1, Executors.newCachedThreadPool()); }
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元

