蒼穹之邊彰阴,浩瀚之摯瘾敢,眰恦之美; 悟心悟性尿这,善始善終簇抵,惟善惟道! —— 朝槿《朝槿兮年說》
寫在開頭
我國宋代禪宗大師青原行思在《三重境界》中有這樣一句話:“ 參禪之初射众,看山是山碟摆,看水是水;禪有悟時(shí)叨橱,看山不是山典蜕,看水不是水断盛;禪中徹悟,看山仍然山愉舔,看水仍然是水郑临。”
作為一名Java Developer屑宠,在面對(duì)Java并發(fā)編程的時(shí)候,有過哪些的疑惑與不解 仇让?對(duì)于Java領(lǐng)域中的線程機(jī)制與多線程典奉,你都做了哪些功課?是否和我一樣丧叽,在看完《Java編程思想》和《Java并發(fā)編程實(shí)戰(zhàn)》之后卫玖,依舊一頭霧水,不知其跡踊淳?那么假瞬,希望你看完此篇文章之后,對(duì)你有所幫助迂尝。
從一定程度上說脱茉,Java并發(fā)編程之路,實(shí)則是一條“看山是山垄开,看山不是山琴许,看山還是山”的修行之路。大多數(shù)情況下溉躲,當(dāng)我們覺得有跡可循到有跡可尋時(shí)榜田,何嘗不是陷入了另外一個(gè)“怪圈”之中?
從搭載Linux系統(tǒng)上的服務(wù)器程序來說锻梳,使用Java編寫的是”單進(jìn)程-多線程"程序,而用C++語言編寫的箭券,可能是“單進(jìn)程-多線程”程序,“多進(jìn)程-單線程”程序或者是“多進(jìn)程-多線程”程序疑枯。其中辩块,“多進(jìn)程-多線程”程序是”單進(jìn)程-多線程"程序和“多進(jìn)程-單線程”程序的組合體。
相對(duì)于操作系統(tǒng)內(nèi)核來說荆永,Java程序?qū)儆趹?yīng)用程序庆捺,只能在這一個(gè)進(jìn)程里面,一般我們都是直接利用JDK提供的API開發(fā)多個(gè)線程實(shí)現(xiàn)并發(fā)屁魏。
而C++直接運(yùn)行在Linux系統(tǒng)上滔以,可以直接利用Linux系統(tǒng)提供的強(qiáng)大的進(jìn)程間通信(Inter-Process Communication,IPC),很容易創(chuàng)建多個(gè)進(jìn)程實(shí)現(xiàn)并發(fā)程序氓拼,并實(shí)現(xiàn)進(jìn)程間通信你画。
但是抵碟,多線程的開發(fā)難度遠(yuǎn)遠(yuǎn)高于單線程的開發(fā),主要是需要處理線程間的通信坏匪,需要對(duì)線程并發(fā)做控制拟逮,需要做好線程間的協(xié)調(diào)工作。
對(duì)于固定負(fù)載情況下适滓,在描述和研究計(jì)算并發(fā)系統(tǒng)處理能力敦迄,以及描述并行處理效果的加速比,一直有一個(gè)比較著名的計(jì)算公式:
就是我們熟知的阿姆達(dá)爾定律(Amdahl"s Law)凭迹,在這個(gè)公式中罚屋,
[1]. P:指的是程序中可并行部分的程序在單核上執(zhí)行的時(shí)間占比。一般用作表示可改進(jìn)性能的部件原先運(yùn)行占用的時(shí)間與系統(tǒng)整體運(yùn)行需要的時(shí)間的比值嗅绸,取值范圍是0 ≤ P ≤ 1脾猛。
[2]. S:指的是處理器的個(gè)數(shù)(總核心數(shù))。一般用作表示升級(jí)加速比鱼鸠,可改進(jìn)部件原先運(yùn)行速度與改進(jìn)后的部件速度的比值猛拴,取值范圍是S ≥ 1。
[3]. Slatency(s):指的是程序在S個(gè)處理器相對(duì)在單個(gè)處理器(單核)中速度提升比率蚀狰。一般用作表示整個(gè)任務(wù)的提速比愉昆。
根據(jù)這個(gè)公式,我們可以依據(jù)可確定程序中可并行代碼的比例麻蹋,來決定我們實(shí)際工作中增加處理器(總核心數(shù))所能帶來的速度提升的上限撼唾。
無論是C++開發(fā)者在Linux系統(tǒng)中使用的pthread,還是Java開發(fā)者使用的java.util.concurrent(JUC)庫哥蔚,這些線程機(jī)制的都需要一定的線程I/O模型來做理論支撐倒谷。
所以,接下來糙箍,我們就讓我們一起探討和揭開Java領(lǐng)域中的線程I/O模型的神秘面紗渤愁,針對(duì)那些盤根錯(cuò)落的枝末細(xì)節(jié),才能讓我們更好地了解和正確認(rèn)識(shí)ava領(lǐng)域中的線程機(jī)制深夯。
關(guān)健術(shù)語
本文用到的一些關(guān)鍵詞語以及常用術(shù)語抖格,主要如下:
- 阿姆達(dá)爾定律(Amdahl 定律): 用于確定并發(fā)系統(tǒng)中性能瓶頸部件在采用措施提示性能后,此部件對(duì)系統(tǒng)性能提示的改進(jìn)程度咕晋,即系統(tǒng)加速比雹拄。
- 任務(wù)(Task): 表示一個(gè)程序需要被完成工作內(nèi)容,與線程非一對(duì)一對(duì)應(yīng)的關(guān)系掌呜,是一個(gè)相對(duì)概念滓玖。
- 并發(fā)(Concurrent): 表示至少一個(gè)任務(wù)或者若干 個(gè)任務(wù)同一個(gè)時(shí)間段內(nèi)被執(zhí)行,但是不是順序執(zhí)行质蕉,大多數(shù)都是以交替的方式被執(zhí)行势篡。
- 并行(Parallel): 表示至少一個(gè)任務(wù)或者若干 個(gè)任務(wù)同一個(gè)時(shí)刻被執(zhí)行翩肌。主要是指一個(gè)并行連接通過多個(gè)通道在同一時(shí)間內(nèi)傳播多個(gè)數(shù)據(jù)流。
- 串行(Serial): 表示至多一個(gè)任務(wù)或者只有一個(gè) 個(gè)任務(wù)同一個(gè)時(shí)刻被執(zhí)行禁悠。主要是指在同一時(shí)間內(nèi)只連接傳輸一個(gè)數(shù)據(jù)流念祭。
- 內(nèi)核線程(Kernel Thread): 表示由內(nèi)核管理的線程,處于操作系統(tǒng)內(nèi)核空間碍侦。用戶應(yīng)用程序通過API和系統(tǒng)調(diào)用(system call)來訪問線程工具粱坤。
- 應(yīng)用線程(Application Thread): 表示不需要內(nèi)核支持而在用戶應(yīng)用程序中實(shí)現(xiàn)的線程,處于應(yīng)用程序空間瓷产,也稱作用戶線程站玄。主要是由JVM管理的線程和JVM自己攜帶的JVM線程。
- 上下文切換(Context Switch): 一般是指任務(wù)切換, 或者CPU寄存器切換拦英。當(dāng)多任務(wù)內(nèi)核決定運(yùn)行另外的任務(wù)時(shí), 它保存正在運(yùn)行任務(wù)的當(dāng)前狀態(tài), 也就是CPU寄存器中的全部?jī)?nèi)容。這些內(nèi)容被保存在任務(wù)自己的堆棧中, 入棧工作完成后就把下一個(gè)將要運(yùn)行的任務(wù)的當(dāng)前狀況從該任務(wù)的棧中重新裝入CPU寄存器, 并開始下一個(gè)任務(wù)的運(yùn)行過程测秸。在Java領(lǐng)域中疤估,線程有生命周期,其上下文信息的保存和恢復(fù)的過程霎冯。
- 線程安全(Thread Safe): 一段操作共享數(shù)據(jù)的代碼能夠保證同一個(gè)時(shí)間內(nèi)被多個(gè)線程執(zhí)行而依然保證其數(shù)據(jù)的正確性的考量铃拇。
基本概述
Java領(lǐng)域中的線程主要分為Java層線程(Java Thread) ,JVM層線程(JVM Thread),操作系統(tǒng)層線程(Kernel Thread)。
對(duì)于Java領(lǐng)域中沈撞,從一定程度上來說慷荔,由于Java程序并不直接運(yùn)行在Linux系統(tǒng)上,而是運(yùn)行在JVM(Java 虛擬機(jī))上缠俺,而一個(gè)JVM實(shí)例是一個(gè)Linux進(jìn)程显晶,每一個(gè)JVM都是一個(gè)獨(dú)立的“沙盒”,JVM之間相互獨(dú)立壹士,互不通信磷雇。
按照操作系統(tǒng)和應(yīng)用程序兩個(gè)層次來說,線程主要可以分為內(nèi)核線程(Kernel Thread) 和應(yīng)用線程(Application Thread)躏救。
其中唯笙,在Java領(lǐng)域中的線程主要分為Java層線程(Java Thread) ,JVM層線程(JVM Thread),操作系統(tǒng)層線程(Kernel Thread)。
一般來說盒使,我們把應(yīng)用線程看作更高層面的線程崩掘,而內(nèi)核線程需要向應(yīng)用線程提供支持。由此可見少办,內(nèi)核線程和應(yīng)用線程之間存在一定的映射關(guān)系苞慢。
因此,從線程映射關(guān)系來看英妓,不同的操作系統(tǒng)可能采用不同的映射方式枉疼,我們把這些映射關(guān)系稱為線程的映射皮假,或者可以說作線程映射理論模型(Thread Mappered Theory Model )。
在Java領(lǐng)域中骂维,對(duì)于文件的I/O操作惹资,提供了一系列的I/O功能API,主要基于基于流模型實(shí)現(xiàn)航闺。我們把這些流模型的設(shè)計(jì)褪测,稱作為I/O流模型(I/O Stream Model )。
其中潦刃,Java對(duì)照操作系統(tǒng)內(nèi)核以及網(wǎng)絡(luò)通信I/O中的傳統(tǒng)BIO來說侮措,提供并支持了NIO和AIO的功能API設(shè)計(jì),我們把這些設(shè)計(jì)乖杠,稱作為線程I/O參考模型(Thread I/O Reference Model )分扎。
另外,對(duì)于NIO和AIO還參考了一定的設(shè)計(jì)模式來實(shí)現(xiàn)胧洒,我們把這些基于設(shè)計(jì)模式的設(shè)計(jì)畏吓,稱作為線程設(shè)計(jì)模式模型(Thread I/O Design Pattern Model )。
綜上所述卫漫,在Java領(lǐng)域中菲饼,我們?cè)趯W(xué)習(xí)和掌握J(rèn)ava并發(fā)編程的時(shí)候,可以按照:線程映射理論模型->I/O流模型->線程I/O參考模型->線程設(shè)計(jì)模式模型->線程價(jià)值模型等脈絡(luò)來一一進(jìn)行對(duì)比分析列赎。
一. Java 領(lǐng)域中的線程映射理論模型
Java 領(lǐng)域中的線程映射模型主要有內(nèi)核級(jí)線程模型(Kernel-Level Thread 宏悦,KLT)、應(yīng)用級(jí)線程模型(Application-Level Thread 包吝,ALT)饼煞、混合兩級(jí)線程模型(Mixture-Level Thread ,MLT)等3種模型诗越。
從Java線程映射類型來看派哲,主要有線程一對(duì)一(1:1)映射,線程多對(duì)多(M:1)映射掺喻,線程多對(duì)多(M:N)映射等關(guān)系芭届。
對(duì)應(yīng)到線程模型來說,線程一對(duì)一(1:1)映射對(duì)應(yīng)著內(nèi)核線程(Kernel-Level Thread 感耙,KLT),線程多對(duì)多(M:1)映射對(duì)應(yīng)著應(yīng)用級(jí)線程(Application-Level Thread褂乍,ALT),線程多對(duì)多(M:N)映射對(duì)應(yīng)著混合兩級(jí)線程(Mixture-Level Thread 即硼,MLT)逃片。
因此,Java領(lǐng)域中實(shí)現(xiàn)多線程主要有3種模型:內(nèi)核級(jí)線程模型、應(yīng)用級(jí)線程模型褥实、混合兩級(jí)線程模型呀狼。它們之間最大的差異就在于線程與內(nèi)核調(diào)度實(shí)體( Kernel Scheduling Entity,簡(jiǎn)稱KSE)之間的對(duì)應(yīng)關(guān)系上。
顧名思義损离,內(nèi)核調(diào)度實(shí)體就是可以被內(nèi)核的調(diào)度器調(diào)度的對(duì)象,因此稱為內(nèi)核級(jí)線程哥艇,是操作系統(tǒng)內(nèi)核的最小調(diào)度單元。
綜上所述僻澎,接下來貌踏,我們來詳細(xì)討論Java 領(lǐng)域中的線程映射理論模型。
1. 應(yīng)用級(jí)線程模型
應(yīng)用級(jí)線程模型主要是指(Application-Level Thread 窟勃,ALT)祖乳,就是多個(gè)用戶線程映射到同一個(gè)內(nèi)核線程上,用戶線程的創(chuàng)建秉氧、調(diào)度眷昆、同步的所有操作全部都是由用戶空間的線程來完成的。
在Java領(lǐng)域中汁咏,應(yīng)用級(jí)線程主要是指Java語言編寫應(yīng)用程序的Java 線程(Java Thread)和JVM虛擬機(jī)中JVM線程(JVM Thread)亚斋。
在應(yīng)用級(jí)線程模型下,完全建立在用戶空間的線程庫上梆暖,不依賴于系統(tǒng)內(nèi)核伞访,用戶線程的創(chuàng)建掂骏、同步轰驳、切換和銷毀等操作完全在用戶態(tài)執(zhí)行,不需要切換到內(nèi)核態(tài)弟灼。
其中级解,用戶進(jìn)程使用系統(tǒng)內(nèi)核提供的接口——輕量級(jí)進(jìn)程(Light Weight Process,LWP)來使用系統(tǒng)內(nèi)核線程田绑。
在此種線程模型下勤哗,由于一個(gè)用戶線程對(duì)應(yīng)一個(gè)LWP,因此某個(gè)LWP在調(diào)用過程中阻塞了不會(huì)影響整個(gè)進(jìn)程的執(zhí)行掩驱。
但是各種線程的操作都需要在用戶態(tài)和內(nèi)核態(tài)之間頻繁切換芒划,消耗太大,速度相對(duì)用戶線程模型來說要慢欧穴。
2. 內(nèi)核級(jí)線程模型
內(nèi)核級(jí)線程模型主要是指(Kernel-Level Thread 民逼,KLT),用戶線程與內(nèi)核線程建立了一對(duì)一的關(guān)系涮帘,即一個(gè)用戶線程對(duì)應(yīng)一個(gè)內(nèi)核線程拼苍,內(nèi)核負(fù)責(zé)每個(gè)線程的調(diào)度。
在Linux中调缨,對(duì)于內(nèi)核級(jí)線程疮鲫,操作系統(tǒng)會(huì)為其創(chuàng)建一套棧:用戶棧+內(nèi)核棧吆你,其中用戶棧工作在用戶態(tài),內(nèi)核棧工作在內(nèi)核態(tài)俊犯,在發(fā)生系統(tǒng)調(diào)用時(shí)妇多,線程的執(zhí)行會(huì)從用戶棧切換到內(nèi)核棧。
在內(nèi)核級(jí)線程模型下瘫析,完全依賴操作系統(tǒng)內(nèi)核提供的內(nèi)核線程來實(shí)現(xiàn)多線程砌梆。線程的切換調(diào)度由系統(tǒng)內(nèi)核完成,系統(tǒng)內(nèi)核負(fù)責(zé)將多個(gè)線程執(zhí)行的任務(wù)映射到各個(gè)CPU中去執(zhí)行贬循。
其中咸包,glibc中的pthread_create方法主要是創(chuàng)建一個(gè)OS內(nèi)核級(jí)線程,我們不深入細(xì)節(jié)杖虾,主要是為該線程分配了棧資源烂瘫;需要注意的是這個(gè)棧資源對(duì)于JVM而言是堆外內(nèi)存,因此堆外內(nèi)存的大小會(huì)影響JVM可以創(chuàng)建的線程數(shù)。
在JVM概念中,JVM棧用來執(zhí)行Java方法籽慢,而本地方法棧用來執(zhí)行native方法辨赐;但需要注意的是JVM只是在概念上區(qū)分了這兩種棧,而并沒有規(guī)定如何實(shí)現(xiàn)闷营。
在HotSpot中,則是將JVM棧與本地方法棧二合一,使用核心線程的用戶棧來實(shí)現(xiàn)(因?yàn)镴VM棧和本地方法棧都是屬于用戶態(tài)的棧)籍琳,即Java方法與native方法都在同一個(gè)用戶棧中調(diào)用,而當(dāng)發(fā)生系統(tǒng)調(diào)用時(shí)贷祈,再切換到核心棧運(yùn)行趋急。
這種設(shè)計(jì)的好處是線程的各種操作以及切換消耗很低;
但是線程的所有操作都需要在用戶態(tài)實(shí)現(xiàn)势誊,線程的調(diào)度實(shí)現(xiàn)起來異常復(fù)雜呜达,并且系統(tǒng)內(nèi)核對(duì)ULT無感知,如果線程阻塞則會(huì)引起整個(gè)進(jìn)程的阻塞粟耻。
3. 混合兩級(jí)線程模型
混合兩級(jí)線程模型主要是指(Mixture-Level Thread 查近,MLT),是應(yīng)用級(jí)線程模型和內(nèi)核級(jí)線程模型等兩種模型的混合版本挤忙,用戶線程仍然是在用戶態(tài)中創(chuàng)建霜威,用戶線程的創(chuàng)建、切換和銷毀的消耗很低饭玲,用戶線程的數(shù)量不受限制侥祭。
對(duì)于混合兩級(jí)線程模型,是應(yīng)用級(jí)線程模型和內(nèi)核級(jí)線程模型等兩種模型的混合版本,主要是充分吸收前面兩種線程模型的優(yōu)點(diǎn)且盡量規(guī)避它們的缺點(diǎn)矮冬。
在此模型下用戶線程與內(nèi)核線程是多對(duì)多(M : N谈宛,通常M >= N)的映射模型。主要是維護(hù)一個(gè)輕量級(jí)進(jìn)程(Light Weight Process胎署,LWP)吆录,在用戶線程和內(nèi)核線程之間充當(dāng)橋梁,就可以使用操作系統(tǒng)提供的線程調(diào)度和處理器映射功能琼牧。
一般來說恢筝,Java虛擬機(jī)使用的線程模型是基于操作系統(tǒng)提供的原生線程模型來實(shí)現(xiàn)的,Windows系統(tǒng)和Linux系統(tǒng)都是使用的內(nèi)核線程模型巨坊,而Solaris系統(tǒng)支持混合線程模型和內(nèi)核線程模型兩種實(shí)現(xiàn)撬槽。
還有,Java線程內(nèi)存模型中趾撵,可以將虛擬機(jī)內(nèi)存劃分為兩部分內(nèi)存:主內(nèi)存和線程工作內(nèi)存侄柔,主內(nèi)存是多個(gè)線程共享的內(nèi)存,線程工作內(nèi)存是每個(gè)線程獨(dú)享的內(nèi)存占调。方法區(qū)和堆內(nèi)存就是主內(nèi)存區(qū)域暂题,而虛擬機(jī)棧、本地方法棧以及程序計(jì)數(shù)器則屬于每個(gè)線程獨(dú)享的工作內(nèi)存究珊。
Java內(nèi)存模型規(guī)定所有成員變量都需要存儲(chǔ)在主內(nèi)存中薪者,線程會(huì)在其工作內(nèi)存中保存需要使用的成員變量的拷貝,線程對(duì)成員變量的操作(讀取和賦值等)都是對(duì)其工作內(nèi)存中的拷貝進(jìn)行操作剿涮。各個(gè)線程之間不能互相訪問工作內(nèi)存言津,線程間變量的傳遞需要通過主內(nèi)存來完成。
二. Java 領(lǐng)域中的I/O流模型
Java 領(lǐng)域中的I/O模型主要指Java 領(lǐng)域中的I/O模型大致可以分為字符流I/O模型幔虏,字節(jié)流I/O模型以及網(wǎng)絡(luò)通信I/O模型纺念。
在編程語言的I/O類庫中常使用流(Stream)這個(gè)概念贝椿,代表了任何有能力產(chǎn)出數(shù)據(jù)的數(shù)據(jù)源對(duì)象或者是有能力接收數(shù)據(jù)的接收端對(duì)象想括。
流是個(gè)抽象的概念,是對(duì)輸入輸出設(shè)備的高度抽象,一般來說烙博,編程語言都會(huì)涉及輸入流和輸出流兩部分瑟蜈。
一定意義上來說,輸入流可以看作一個(gè)輸入通道渣窜,輸出流可以看作一個(gè)輸出通道铺根,其中:
- 輸入流是相對(duì)程序而言的,外部傳入數(shù)據(jù)給程序需要借助輸入流乔宿。
- 輸出流是相對(duì)程序而言的位迂,程序把數(shù)據(jù)傳輸?shù)酵獠啃枰柚敵隽鳌?/li>
由于,“流”模型屏蔽了實(shí)際的I/O設(shè)備中處理數(shù)據(jù)的細(xì)節(jié),這就意味著我們只需要根據(jù)相關(guān)的基礎(chǔ)API的功能和設(shè)計(jì)掂林,便可實(shí)現(xiàn)數(shù)據(jù)處理和交互臣缀。
Java IO 方式有很多種,基于不同的 IO 抽象模型和交互方式泻帮,可以進(jìn)行簡(jiǎn)單區(qū)分:
第一精置,傳統(tǒng)的 java.io 包,它基于流模型實(shí)現(xiàn)锣杂,提供了我們最熟知的一些 IO 功能脂倦,比如 File 抽象、輸入輸出流等元莫。交互方式是同步赖阻、阻塞的方式,也就是說踱蠢,在讀取輸入流或者寫入輸出流時(shí)政供,在讀、寫動(dòng)作完成之前朽基,線程會(huì)一直阻塞在那里布隔,它們之間的調(diào)用是可靠的線性順序。java.io 包的好處是代碼比較簡(jiǎn)單稼虎、直觀衅檀,缺點(diǎn)則是 IO 效率和擴(kuò)展性存在局限性,容易成為應(yīng)用性能的瓶頸霎俩。
很多時(shí)候哀军,人們也把 java.net 下面提供的部分網(wǎng)絡(luò) API,比如 Socket打却、ServerSocket杉适、HttpURLConnection 也歸類到同步阻塞 IO 類庫,因?yàn)榫W(wǎng)絡(luò)通信同樣是 IO 行為柳击。
第二猿推,在 Java 1.4 中引入了 NIO 框架(java.nio 包),提供了 Channel捌肴、Selector蹬叭、Buffer 等新的抽象,可以構(gòu)建多路復(fù)用的状知、同步非阻塞 IO 程序秽五,同時(shí)提供了更接近操作系統(tǒng)底層的高性能數(shù)據(jù)操作方式。
第三饥悴,在 Java 7 中坦喘,NIO 有了進(jìn)一步的改進(jìn)盲再,也就是 NIO 2,引入了異步非阻塞 IO 方式瓣铣,也有很多人叫它 AIO(Asynchronous IO)洲胖。異步 IO 操作基于事件和回調(diào)機(jī)制,可以簡(jiǎn)單理解為坯沪,應(yīng)用操作直接返回绿映,而不會(huì)阻塞在那里,當(dāng)后臺(tái)處理完成腐晾,操作系統(tǒng)會(huì)通知相應(yīng)線程進(jìn)行后續(xù)工作叉弦。
其中,Java類庫中的I/O類分成輸入和輸出兩部分藻糖,主要是對(duì)應(yīng)著實(shí)現(xiàn)我們與計(jì)算機(jī)操作交互時(shí)的一種規(guī)范和約束淹冰,但是對(duì)于不同的數(shù)據(jù)有著不同的實(shí)現(xiàn)。
綜上所述巨柒,Java 領(lǐng)域中的I/O模型大致可以分為字符流I/O模型樱拴,字節(jié)流I/O模型以及網(wǎng)絡(luò)通信I/O模型等3類。
1. 字節(jié)流I/O模型
字節(jié)流I/O模型是指在I/O操作洋满,數(shù)據(jù)傳輸過程中晶乔,傳輸數(shù)據(jù)的最基本單位是字節(jié)的流,按照8位傳輸字節(jié)為單位輸入/輸出數(shù)據(jù)牺勾。
在Java 領(lǐng)域中正罢,對(duì)字節(jié)流的類通常以stream結(jié)尾,對(duì)于字節(jié)數(shù)據(jù)的操作驻民,提供了輸入流(InputStream)翻具、輸出流(OutputStream)這樣式的設(shè)計(jì),是用于讀取或?qū)懭胱止?jié)的基礎(chǔ)API回还,一般常用于操作類似文本或者圖片文件裆泳。
2. 字符流I/O模型
字符流I/O模型是指在I/O操作,數(shù)據(jù)傳輸過程中柠硕,傳輸數(shù)據(jù)的最基本單位是字符的流工禾,按照16位傳輸字符為單位輸入/輸出數(shù)據(jù)。
在Java 領(lǐng)域中仅叫,對(duì)字符流的類通常以reader和writer結(jié)尾帜篇,對(duì)于字節(jié)數(shù)據(jù)的操作糙捺,提供了輸入流(Reader)诫咱、輸出流(Writer)這樣式的設(shè)計(jì),是用于讀取或?qū)懭胱止?jié)的基礎(chǔ)API洪灯,一般常用于類似從文件中讀取或者寫入文本信息坎缭。
3. 網(wǎng)絡(luò)通信I/O模型
網(wǎng)絡(luò)通信I/O模型是指java.net 下竟痰,提供的部分網(wǎng)絡(luò) API,比如 Socket掏呼、ServerSocket坏快、HttpURLConnection 等IO 類庫,實(shí)現(xiàn)網(wǎng)絡(luò)通信同樣是 IO 行為憎夷。
在Java領(lǐng)域中莽鸿,NIO提供了與傳統(tǒng)BIO模型中的Socket和ServerSocket相對(duì)應(yīng)的SocketChannel和ServerSocketChannel兩種不同的套接字通道實(shí)現(xiàn)。SocketChannel可以看作是 socket 的一個(gè)完善類拾给,除了提供 Socket 的相關(guān)功能外祥得,還提供了許多其他特性,如后面要講到的向選擇器注冊(cè)的功能蒋得。
其中级及,新增的SocketChannel和ServerSocketChannel兩種通道都支持阻塞和非阻塞兩種模式。
三. Java 領(lǐng)域中的線程I/O參考模型
在Java領(lǐng)域中额衙,我們對(duì)照線程概念(單線程和多線程)來說饮焦,可以分為Java 線程-阻塞I/O模型和Java 線程-非阻塞I/O模型兩種。
由于阻塞與非阻塞主要是針對(duì)于應(yīng)用程序?qū)τ谙到y(tǒng)函數(shù)調(diào)用角度來限定的窍侧,從阻塞與非阻塞的意義上來說县踢,I/O可以分為阻塞I/O和非阻塞I/O兩種大類。其中:
- 阻 塞 I/O : 進(jìn)行I/O操作時(shí)伟件,使當(dāng)前線程進(jìn)入阻塞狀態(tài)殿雪,從具體應(yīng)用程序來看,如果當(dāng)一次I/O操作(Read/Write)沒有就緒或者沒有完成锋爪,則函數(shù)調(diào)用則會(huì)一直處于等待狀態(tài)丙曙。
- 非阻塞I/O:進(jìn)行I/O操作時(shí),使當(dāng)前線程不進(jìn)入阻塞狀態(tài)其骄,從具體應(yīng)用程序來看亏镰,如果當(dāng)一次I/O操作(Read/Write)即使沒有就緒或者沒有完成,則函數(shù)調(diào)用立即返回結(jié)果拯爽,然后由應(yīng)用程序輪詢處理索抓。
而同步與異步主要正針對(duì)應(yīng)用程序?qū)τ谙到y(tǒng)函數(shù)調(diào)用后,其I/O操作中讀/寫(Read/Write)是由誰完成來限定的毯炮,I/O可以分為同步I/O和異步I/O兩種大類逼肯。其中:
- 同步I/O: 進(jìn)行I/O操作時(shí),可以使當(dāng)前線程進(jìn)入進(jìn)入阻塞或或非阻塞狀態(tài)桃煎,從具體應(yīng)用程序來看篮幢,如果當(dāng)一次I/O操作(Read/Write)都是托管給應(yīng)用程序來完成。
- 異步I/O: 進(jìn)行I/O操作時(shí)为迈,可以使當(dāng)前線程進(jìn)入進(jìn)入非阻塞狀態(tài)三椿,從具體應(yīng)用程序來看缺菌,如果當(dāng)一次I/O操作(Read/Write)都是托管給操作系統(tǒng)來完成,完成后回調(diào)或者事件通知應(yīng)用程序搜锰。
由此可見伴郁,按照這些個(gè)定義可以知道:
- 當(dāng)程序在執(zhí)行I/O操作時(shí),經(jīng)典的網(wǎng)絡(luò)I/O操作(Read/Write)場(chǎng)景蛋叼,主要可以分為阻塞I/O焊傅,非阻塞I/O,單線程以及多線程等場(chǎng)景狈涮。
- 異步I/O一定是非阻塞I/O租冠,不存在是異步還阻塞的情況;同步I/O可能存在阻塞或或非阻塞的情況薯嗤,還有可能是I/O線程多路復(fù)用的情況顽爹。
因此,我們可以對(duì)其線程I/O模型來說骆姐,I/O可以分為同步-阻塞I/O和同步-非阻塞I/O镜粤,以及異步I/O等3種,其中I/O多路復(fù)用屬于同步-阻塞I/O玻褪。
綜上所所述,肉渴,在Java領(lǐng)域中,我們對(duì)照線程概念(單線程和多線程)來說带射,可以分為Java 線程-阻塞I/O模型和Java 線程-非阻塞I/O模型兩種同规。接下來,我們就詳細(xì)地來探討一下窟社。
(一). Java 線程阻塞I/O模型
Java 線程-阻塞I/O模型主要可以分為單線程阻塞I/O模型和多線程阻塞I/O模型券勺。
從一個(gè)服務(wù)器處理客戶端連接來說,單線程情況下灿里,一般都是以一個(gè)線程負(fù)責(zé)處理所有客戶端連接的I/O操作(Read/Write)操作关炼。
程序在執(zhí)行I/O操作,一般都是從內(nèi)核空間復(fù)制數(shù)據(jù)匣吊,但內(nèi)核空間的數(shù)據(jù)可能需要很長(zhǎng)的時(shí)間去準(zhǔn)備數(shù)據(jù)儒拂,由此很有可能導(dǎo)致用戶空間產(chǎn)生阻塞。
其產(chǎn)生阻塞的過程色鸳,主要如下:
- 應(yīng)用程序發(fā)起I/O操作(Read/Write)之后社痛,進(jìn)入阻塞狀態(tài),然后提交給操作系統(tǒng)內(nèi)核完成I/O操作命雀。
- 當(dāng)內(nèi)核沒有準(zhǔn)備數(shù)據(jù)蒜哀,需要不斷從網(wǎng)絡(luò)中讀取數(shù)據(jù),一旦準(zhǔn)備就緒咏雌,則將數(shù)據(jù)復(fù)制到用戶空間供應(yīng)用程序使用凡怎。
- 應(yīng)用程序從發(fā)起讀取數(shù)據(jù)操作到繼續(xù)執(zhí)行后續(xù)處理的這段時(shí)間校焦,便是我們說的阻塞狀態(tài)赊抖。
由此可見统倒,引入Java線程的概念,我們可以把Java 線程-阻塞I/O模型主要可以分為單線程阻塞I/O模型和多線程阻塞I/O模型氛雪。
1. 單線程阻塞I/O模型
單線程阻塞I/O模型主要是指對(duì)于多個(gè)客戶端訪問時(shí)房匆,只能同時(shí)處理一個(gè)客戶端的訪問,并且在I/O操作上是阻塞的报亩,線程會(huì)一直處于等待狀態(tài)浴鸿,直到當(dāng)前線程中前一個(gè)客戶端訪問結(jié)束后,才繼續(xù)開始下一個(gè)客戶端的訪問弦追。
單線程阻塞I/O模型是最簡(jiǎn)單的服務(wù)器模型岳链,是Java Developer面對(duì)網(wǎng)絡(luò)編程最基礎(chǔ)的模型。
由于對(duì)于多個(gè)客戶端訪問時(shí)劲件,只能同時(shí)處理一個(gè)客戶端的訪問掸哑,并且在I/O操作上是阻塞的,線程會(huì)一直處于等待狀態(tài)零远,直到當(dāng)前線程中前一個(gè)客戶端訪問結(jié)束后苗分,才繼續(xù)開始下一個(gè)客戶端的訪問。
也就意味著牵辣,客戶端的訪問請(qǐng)求需要一個(gè)一個(gè)排隊(duì)等待摔癣,只提供一問一答的服務(wù)機(jī)制。
這種模型的特點(diǎn)纬向,主要在于單線程和阻塞I/O择浊。其中:
- 單線程 :指的是服務(wù)器端只有一個(gè)線程處理客戶端的請(qǐng)求,客戶端連接與服務(wù)器端的處理線程比例關(guān)系為N:1,無法同時(shí)處理多個(gè)連接逾条,只能串行方式連接處理近她。
- 阻塞I/O:服務(wù)器在I/O操作(Read/Write)操作時(shí)是阻塞的,主要表現(xiàn)在讀取客戶端數(shù)據(jù)時(shí)膳帕,需要等待客戶端發(fā)送數(shù)據(jù)并且把操作系統(tǒng)內(nèi)核中的數(shù)據(jù)復(fù)制到用戶空間中的用戶線程中粘捎,完成后才解除阻塞狀態(tài);同時(shí)危彩,數(shù)據(jù)回寫客戶端要等待用戶進(jìn)程把數(shù)據(jù)寫入到操作系統(tǒng)系統(tǒng)內(nèi)核后才解除阻塞狀態(tài)攒磨。
綜上所述,單線程阻塞I/O模型最明顯的特點(diǎn)就是服務(wù)機(jī)制簡(jiǎn)單汤徽,服務(wù)器的系統(tǒng)資源開銷小娩缰,但是并發(fā)能力低,容錯(cuò)能力也低谒府。
2. 多線程阻塞I/O模型
多線程阻塞I/O模型主要是指對(duì)于多個(gè)客戶端訪問時(shí)拼坎,利用多線程機(jī)制為每一個(gè)客戶端的訪問分配獨(dú)立線程浮毯,實(shí)現(xiàn)同時(shí)處理,并且在I/O操作上是阻塞的泰鸡,線程不會(huì)一直處于等待狀態(tài)债蓝,而是并發(fā)處理客戶端的請(qǐng)求訪問。
多線程阻塞I/O模型是針對(duì)于單線程阻塞I/O模型的缺點(diǎn)盛龄,對(duì)其進(jìn)行多線程化改進(jìn)饰迹,使之能對(duì)于多個(gè)客戶端的請(qǐng)求訪問實(shí)現(xiàn)并發(fā)響應(yīng)處理。
也就意味著余舶,客戶端的訪問請(qǐng)求不需要一個(gè)一個(gè)排隊(duì)等待啊鸭,利用多線程機(jī)制為每一個(gè)客戶端的訪問分配獨(dú)立線程渔彰。
這種模型的特點(diǎn)媒怯,主要在于多線程和阻塞I/O。其中:
- 多線程 :指的是服務(wù)器端至少有一個(gè)線程或者若干個(gè)線程處理客戶端的請(qǐng)求经柴,客戶端連接與服務(wù)器端的處理線程比例關(guān)系為M:N,并發(fā)同時(shí)處理多個(gè)連接挟憔,可以并行方式連接處理钟些。但客戶端連接與服務(wù)器處理線程的關(guān)系是一對(duì)一的。
- 阻塞I/O:服務(wù)器在I/O操作(Read/Write)操作時(shí)是阻塞的曲楚,主要表現(xiàn)在讀取客戶端數(shù)據(jù)時(shí)厘唾,需要等待客戶端發(fā)送數(shù)據(jù)并且把操作系統(tǒng)內(nèi)核中的數(shù)據(jù)復(fù)制到用戶空間中的用戶線程中,完成后才解除阻塞狀態(tài)龙誊;同時(shí)抚垃,數(shù)據(jù)回寫客戶端要等待用戶進(jìn)程把數(shù)據(jù)寫入到操作系統(tǒng)系統(tǒng)內(nèi)核后才解除阻塞狀態(tài)。
綜上所述趟大,多線程阻塞I/O模型最明顯的特點(diǎn)就是支持多個(gè)客戶端并發(fā)響應(yīng)鹤树,處理能力得到極大提高,有一定的并發(fā)能力和容錯(cuò)能力逊朽,但是服務(wù)器資源消耗較大罕伯,且多線程之間會(huì)產(chǎn)生線程切換成本,結(jié)構(gòu)也比較復(fù)雜叽讳。
(二). Java 線程非阻塞I/O模型
Java 線程-非阻塞I/O模型主要可以分為應(yīng)用層I/O多路復(fù)用模型和內(nèi)核層I/O多路復(fù)用模型追他,以及內(nèi)核回調(diào)事件驅(qū)動(dòng)I/O模型。
從一個(gè)服務(wù)器處理客戶端連接來說岛蚤,多線程情況下邑狸,一般都是至少一個(gè)線程或者若干個(gè)線程負(fù)責(zé)處理所有客戶端連接的I/O操作(Read/Write)操作。
非阻塞I/O模型與阻塞I/O模型涤妒,相同的地方在于是程序在執(zhí)行I/O操作单雾,一般都是從內(nèi)核空間和應(yīng)用空間復(fù)制數(shù)據(jù)。
與之不同的是,非阻塞I/O模型不會(huì)一直等到內(nèi)核空間準(zhǔn)備好數(shù)據(jù)硅堆,而是立即返回去做其他的事屿储,因此不會(huì)產(chǎn)生阻塞。其中:
應(yīng)用程序中的用戶線程包含一個(gè)緩沖區(qū)渐逃,單個(gè)線程會(huì)不斷輪詢客戶端够掠,以及不斷嘗試進(jìn)行I/O(Read/Write)操作。
一旦內(nèi)核準(zhǔn)好數(shù)據(jù)朴乖,應(yīng)用程序中的用戶線程就會(huì)把數(shù)據(jù)復(fù)制到用戶空間使用祖屏。
由此可見助赞,我們可以把Java 線程-非阻塞I/O模型主要可以分為應(yīng)用層I/O多路復(fù)用模型和內(nèi)核層I/O多路復(fù)用模型买羞,以及內(nèi)核回調(diào)事件驅(qū)動(dòng)I/O模型。
1. 應(yīng)用層I/O多路復(fù)用模型
應(yīng)用層I/O多路復(fù)用模型主要是指當(dāng)多個(gè)客戶端向服務(wù)器發(fā)出請(qǐng)求時(shí)雹食,服務(wù)器會(huì)將每一個(gè)客戶端連接維護(hù)到一個(gè)socket列表中畜普,應(yīng)用程序中的用戶線程會(huì)不斷輪詢sockst列表中的客戶端連接請(qǐng)求訪問,并嘗試進(jìn)行讀寫群叶。
應(yīng)用層I/O多路復(fù)用模型最大的特點(diǎn)就是吃挑,不論有多少個(gè)socket連接,都可以使用應(yīng)用程序中的用戶線程的一個(gè)線程來管理街立。
這個(gè)線程負(fù)責(zé)輪詢socket列表舶衬,不斷進(jìn)行嘗試進(jìn)行I/O(Read/Write)操作,其中:
- I/O(Read)操作:如果成功讀取數(shù)據(jù)赎离,則對(duì)數(shù)據(jù)進(jìn)行處理逛犹。反之,如果失敗梁剔,則下一個(gè)循環(huán)再繼續(xù)嘗試虽画。
- I/O(Write)操作:需要先嘗試把數(shù)據(jù)寫入指定的socket,直到調(diào)用成功結(jié)束荣病。反之码撰,如果失敗,則下一個(gè)循環(huán)再繼續(xù)嘗試个盆。
這種模型脖岛,雖然很好地利用了阻塞的時(shí)間,使得批處理能提升颊亮。但是由于不斷輪詢sockst列表柴梆,同時(shí)也需要處理數(shù)據(jù)的拼接。
2. 內(nèi)核層I/O多路復(fù)用模型
內(nèi)核層I/O多路復(fù)用模型主要是指當(dāng)多個(gè)客戶端向服務(wù)器發(fā)出請(qǐng)求時(shí)编兄,服務(wù)器會(huì)將每一個(gè)客戶端連接維護(hù)到一個(gè)socket列表中轩性,操作系統(tǒng)內(nèi)核不斷輪詢sockst列表,并把遍歷結(jié)果組織羅列成一系列的事件,并驅(qū)動(dòng)事件返回到應(yīng)用層處理揣苏,最后托管給應(yīng)用程序中的用戶線程按照需要處理對(duì)應(yīng)的事件對(duì)象悯嗓。
內(nèi)核層I/O多路復(fù)用模型與應(yīng)用層I/O多路復(fù)用模型,最大的不同就是卸察,輪詢sockst列表是操作系統(tǒng)內(nèi)核來完成的脯厨,有助于檢測(cè)效率。
操作系統(tǒng)內(nèi)核負(fù)責(zé)輪詢socket列表的過程坑质,其中:
- 首先合武,最主要的就是將所有連接的標(biāo)記為可讀事件和可寫事件列表,最后傳入到應(yīng)用程序的用戶空間處理涡扼。
- 然后稼跳,操作系統(tǒng)內(nèi)核復(fù)制數(shù)據(jù)到應(yīng)用層的用戶空間的用戶線程,會(huì)隨著socket數(shù)量的增加吃沪,也會(huì)形成不小的開銷汤善。
- 另外,當(dāng)活躍連接數(shù)比較少時(shí)票彪,內(nèi)核空間和用戶空間會(huì)存在很多無效的數(shù)據(jù)副本红淡,并且不管是否活躍,都會(huì)復(fù)制到用戶空間的應(yīng)用層降铸。
3. 內(nèi)核回調(diào)事件驅(qū)動(dòng)I/O模型
內(nèi)核回調(diào)事件驅(qū)動(dòng)I/O模型主要是指當(dāng)多個(gè)客戶端向服務(wù)器發(fā)出請(qǐng)求時(shí)在旱,服務(wù)器會(huì)將每一個(gè)客戶端連接維護(hù)到一個(gè)socket列表中,操作系統(tǒng)內(nèi)核不斷輪詢sockst列表推掸,利用回調(diào)函數(shù)來檢測(cè)socket列表是否可讀可寫的一種事件驅(qū)動(dòng)I/O機(jī)制桶蝎。
不論是內(nèi)核層的輪詢sockst列表,還是應(yīng)用層的輪詢sockst列表终佛,通過循環(huán)遍歷的方式來檢測(cè)socket列表是否可讀可寫的操作方式俊嗽,其效率都比較低效。
為了尋求一種高效的機(jī)制來優(yōu)化循環(huán)遍歷方式铃彰,因此绍豁,提出了會(huì)回調(diào)函數(shù)事件驅(qū)動(dòng)機(jī)制。其中牙捉,主要是:
- 內(nèi)核空間:當(dāng)客戶端往socket發(fā)送數(shù)據(jù)時(shí)竹揍,內(nèi)核中socket都對(duì)應(yīng)著一個(gè)回調(diào)函數(shù),內(nèi)核就可以直接從網(wǎng)卡中接收數(shù)據(jù)后邪铲,直接調(diào)用回調(diào)函數(shù)芬位。
- 應(yīng)用空間:回調(diào)函數(shù)會(huì)維護(hù)一個(gè)事件列表,應(yīng)用層則獲取事件即可以得到感興趣的事件带到,然后進(jìn)行后續(xù)操作昧碉。
一般來說,內(nèi)核回調(diào)事件驅(qū)動(dòng)的方式主要有2種:
- 第一種:利用可讀列表(ReadList)和可寫列表(WriteList)來標(biāo)記讀事件(Read-Event)/寫事件(Write-Event)來進(jìn)行I/O(Read/Write)操作。
- 第二種:利用在應(yīng)用層中直接指定socket感興趣的事件被饿,通過維護(hù)事件列表(EventList)再來進(jìn)行I/O(Read/Write)操作四康。
綜上所述,這兩種方式都是有操作系統(tǒng)內(nèi)核維護(hù)客戶端中的所有連接狭握,再通過回調(diào)函數(shù)不斷更新事件列表闪金,應(yīng)用空間中的應(yīng)用層的用戶線程只需要根據(jù)輪詢遍歷事件列表即可知道是否進(jìn)行I/O(Read/Write)操作。
由此可見论颅,這種方式極大地提高了檢測(cè)效率哎垦,也增強(qiáng)了數(shù)據(jù)處理能力。
特別指出恃疯,在Java領(lǐng)域中漏设,非阻塞I/O的實(shí)現(xiàn)完全是基于操作系統(tǒng)內(nèi)核的非阻塞I/O,Java把操作系統(tǒng)中的非阻塞I/O的差異最大限度的屏蔽并提供了統(tǒng)一的API澡谭,JDK自己會(huì)幫助我們選擇非阻塞I/O的實(shí)現(xiàn)方式愿题。
一般來說损俭,在Linux系統(tǒng)中蛙奖,只要支持epoll,JDK會(huì)優(yōu)先選擇epoll來實(shí)現(xiàn)Java的非阻塞I/O杆兵。
(三). Java 線程異步I/O模型
Java 線程異步I/O模型主要是指異步非阻塞模型(AIO模型)雁仲, 需要操作系統(tǒng)負(fù)責(zé)將數(shù)據(jù)讀寫到應(yīng)用傳遞進(jìn)來的緩沖區(qū)供應(yīng)用程序操作。
對(duì)于非阻塞I/O模型(NIO)來說琐脏,異步I/O模型的工作機(jī)制來說攒砖,與之不同的是采用“訂閱(Subscribe)-通知(Notification)”模式,主要如下:
- 訂閱(Subscribe): 用戶線程通過操作系統(tǒng)調(diào)用日裙,向內(nèi)核注冊(cè)某個(gè)IO操作后吹艇,即應(yīng)用程序向操作系統(tǒng)注冊(cè)IO監(jiān)聽,然后繼續(xù)做自己的事情昂拂。
- 通知(Notification):當(dāng)操作系統(tǒng)發(fā)生IO事件受神,并且準(zhǔn)備好數(shù)據(jù)后,即內(nèi)核在整個(gè)IO操作(包括數(shù)據(jù)準(zhǔn)備格侯、數(shù)據(jù)復(fù)制)完成后鼻听,再主動(dòng)通知應(yīng)用程序,觸發(fā)相應(yīng)的函數(shù)联四,執(zhí)行后續(xù)的業(yè)務(wù)操作撑碴。
在異步IO模型中,整個(gè)內(nèi)核的數(shù)據(jù)處理過程中朝墩,包括內(nèi)核將數(shù)據(jù)從網(wǎng)絡(luò)物理設(shè)備(網(wǎng)卡)讀取到內(nèi)核緩存區(qū)醉拓、將內(nèi)核緩沖區(qū)的數(shù)據(jù)復(fù)制到用戶緩沖區(qū),用戶程序都不需要阻塞。
由此可見亿卤,異步I/O模型(AIO模型)需要依賴操作系統(tǒng)的支持玫镐,CPU資源開銷比較大,最大的特性是異步能力怠噪,對(duì)socket和I/O起作用恐似,適合連接數(shù)目比較多以及連接時(shí)間長(zhǎng)的系統(tǒng)架構(gòu)。
一般來說傍念,在操作系統(tǒng)里矫夷,異步IO是指Windows系統(tǒng)的IOCP(Input/Output Completion Port),或者C++的網(wǎng)絡(luò)庫asio憋槐。
在Linux系統(tǒng)中双藕,aio雖然是異步IO模型的具體實(shí)現(xiàn),但是由于不成熟阳仔,現(xiàn)在大部分還是依據(jù)是否支持epoll等忧陪,來模擬和封裝epoll實(shí)現(xiàn)的。
在Java領(lǐng)域中近范,支持異步I/O模型(AIO模型)是Jdk 1.7版本開始的嘶摊,基于CompletionHandler接口來實(shí)現(xiàn)操作完成回調(diào),其中分別有三個(gè)新的異步通道评矩,AsynchronousFileChannel叶堆,AsynchronousSocketChannel和AsynchronousServerSocketChannel。
但是斥杜,對(duì)于支持異步編程模式是在Jdk 1.5版本就已經(jīng)存在虱颗,最典型的就是基于Future模型實(shí)現(xiàn)的Executor和FutureTask。
由于Future模型存在一定的局限性蔗喂,在JDK 1.8 之后忘渔,對(duì)Future的擴(kuò)展和增強(qiáng)實(shí)現(xiàn)又新增了一個(gè)CompletableFuture。
由此可見缰儿,在Java領(lǐng)域中畦粮,對(duì)于異步I/O模型提供了異步文件通道(AsynchronousFileChannel)和異步套接字通道(AsynchronousSocketChannel和AsynchronousServerSocketChannel)的實(shí)現(xiàn)。 其中:
- 首先返弹,對(duì)于異步文件通道的實(shí)現(xiàn)锈玉,提供兩種方式獲取操作結(jié)果:
- 通過java.util.concurrent.Future類來表示異步操作的結(jié)果:
- 在執(zhí)行異步操作的時(shí)候傳入一個(gè)java.nio.channels.CompletionHandler接口的實(shí)現(xiàn)類作為操作完成的回調(diào)。
- 其次义起,對(duì)于異步套接字通道的是實(shí)現(xiàn):
- 異步Socket Channel是被動(dòng)執(zhí)行對(duì)象拉背,不需要像 NIO編程那樣創(chuàng)建一個(gè)獨(dú)立的 I/O線程來處理讀寫操作。
- 對(duì)于AsynchronousServerSocketChannel和AsynchronousSocketChannel 都由JDK底層的線程池負(fù)責(zé)回調(diào)并驅(qū)動(dòng)讀寫操作默终。
- 異步套接字通道是真正的異步非阻塞I/O,它對(duì)應(yīng)UNIX網(wǎng)絡(luò)編程中的事件驅(qū)動(dòng)I/O (AIO),它不需要通過多路復(fù)用器(Selector)對(duì)注冊(cè)的通道進(jìn)行輪詢操作即可實(shí)現(xiàn)異步讀寫椅棺, 從而簡(jiǎn)化了 NIO的編程模型犁罩。
綜上所述,對(duì)于在Java領(lǐng)域中的異步IO模型两疚,我們?cè)谑褂玫臅r(shí)候床估,需要依據(jù)實(shí)際業(yè)務(wù)場(chǎng)景需要而進(jìn)行選擇和考量。
??[特別注意]:
[1].IOCP: 輸入輸出完成端口(Input/Output Completion Port诱渤,IOCP), 是支持多個(gè)同時(shí)發(fā)生的異步I/O操作的應(yīng)用程序編程接口丐巫。
[2].epoll: Linux系統(tǒng)中I/O多路復(fù)用實(shí)現(xiàn)方式的一種,主要是(select,poll,epoll)勺美。都是同步I/O递胧,同時(shí)也是阻塞I/O。
[3].Future: 屬于Java JDK 1.5 版本支持的編程異步模型赡茸,在包java.util.concurrent.下面缎脾。
[4].CompletionHandler: 屬于Java JDK 1.7 版本支持的編程異步I/O模型,在包java.nio.channels.下面占卧。
[5].CompletableFuture: 屬于Java JDK 1.8 版本對(duì)Future的擴(kuò)展和增強(qiáng)實(shí)現(xiàn)編程異步I/O模型遗菠,在java.util.concurrent.下面。
四. Java 領(lǐng)域中的線程設(shè)計(jì)模型
Java 領(lǐng)域中的線程設(shè)計(jì)模型最典型就是基于Reactor模式設(shè)計(jì)的非阻塞I/O模型和 基于Proactor 模式設(shè)計(jì)的異步I/O模型和基于Promise模式的Promise模型华蜒。
在Java領(lǐng)域中辙纬,對(duì)于并發(fā)編程的支持,不僅提供了線程機(jī)制友多,也引入了多線程機(jī)制牲平,還有許多同步和異步的實(shí)現(xiàn)。
單從設(shè)計(jì)原則和實(shí)現(xiàn)來說域滥,都采用了許多設(shè)計(jì)模式,其中多線程機(jī)制最常見的就是線程池模式蜈抓。
對(duì)于非阻塞I/O模型启绰,主要采用基于Reactor模式設(shè)計(jì),而異步I/O模型沟使,主要采用基于Proactor 模式設(shè)計(jì)委可。
當(dāng)然,還有基于Promise模式的異步編程模型腊嗡,不過這算是一個(gè)特例着倾。
綜上所述,Java 領(lǐng)域中的線程設(shè)計(jì)模型最典型就是基于Reactor模式設(shè)計(jì)的非阻塞I/O模型和 基于Proactor 模式設(shè)計(jì)的異步I/O模型和基于Promise模式的Promise模型燕少。
1. 多線程非阻塞I/O模型
多線程非阻塞I/O模型是針對(duì)于多線程機(jī)制而設(shè)計(jì)的卡者,根據(jù)CPU的數(shù)量來創(chuàng)建線程數(shù),并且能夠讓多個(gè)線程并行執(zhí)行的非阻塞I/O模型客们。
v8Q55D.png
現(xiàn)在的計(jì)算機(jī)大多數(shù)都是多核CPU的崇决,而且操作系統(tǒng)都提供了多線程機(jī)制材诽,但是我們也沒有辦法抹掉單線程的優(yōu)勢(shì)。
單線程最大的優(yōu)勢(shì)就是一個(gè)CPU只負(fù)責(zé)一個(gè)線程恒傻,對(duì)于多線程中出現(xiàn)的疑難雜癥脸侥,它都可以避免,而且編碼簡(jiǎn)單盈厘。
在一個(gè)線程對(duì)應(yīng)一個(gè)CPU的情況下睁枕,如果多核計(jì)算機(jī)中 只執(zhí)行一個(gè)線程,那么就只有一個(gè)CPU工作沸手,無法充分發(fā)揮CPU和優(yōu)勢(shì)譬重,且資源也無法充分利用。
因此罐氨,我們的程序則可以根據(jù)CPU的數(shù)量來創(chuàng)建線程數(shù)臀规,N個(gè)CPU對(duì)應(yīng)多個(gè)N個(gè)線程,便可以充分利用多個(gè)CPU栅隐。同時(shí)也保持了單線程的特點(diǎn)塔嬉,相當(dāng)于多個(gè)線程并行執(zhí)行而不是并發(fā)執(zhí)行。
在多核計(jì)算機(jī)時(shí)代租悄,多線程和非阻塞都是提升服務(wù)器處理性能的利器谨究。一般我們都是將客戶端連接按照分組分配給至少一個(gè)線程或者若干線程,每個(gè)線程負(fù)責(zé)處理對(duì)應(yīng)組的連接泣棋。
在Java領(lǐng)域中胶哲,最常見的多線程阻塞I/O模型就是基于Reactor模式的Reactor模型。
2. 基于Reactor模式的Reactor模型
Reactor模型是指在事件驅(qū)動(dòng)的思想上潭辈,基于Reactor的工作模式而設(shè)計(jì)的非阻塞I/O模型(NIO 模型)鸯屿。一定程度上來說,可以說是主動(dòng)模式I/O模型把敢。
對(duì)于Reactor模式寄摆,我特意在網(wǎng)上查詢了一下資料,查詢的結(jié)果都是無疾而終修赞,解釋更是五花八門的婶恼。最后,參考一些資料整理得出結(jié)論柏副。
引用一下Doug Lea大師在文章“Scalable IO in Java”中對(duì)Reactor模式的定義:
Reactor模式由Reactor線程勾邦、Handlers處理器兩大角色組成,兩大角色的職責(zé)分別如下:
- Reactor線程的職責(zé):負(fù)責(zé)響應(yīng)IO事件割择,并且分發(fā)到Handlers處理器眷篇。
- Handlers處理器的職責(zé):非阻塞的執(zhí)行業(yè)務(wù)處理邏輯。
個(gè)人理解锨推,Reactor模式是指在事件驅(qū)動(dòng)的思想上铅歼,通過一個(gè)或多個(gè)輸入同時(shí)傳遞給服務(wù)處理器的服務(wù)請(qǐng)求的事件驅(qū)動(dòng)處理模式公壤。其中,基本思想有兩個(gè):
基于 I/O 復(fù)用模型:多個(gè)連接共用一個(gè)阻塞對(duì)象椎椰,應(yīng)用程序只需要在一個(gè)阻塞對(duì)象等待厦幅,無需阻塞等待所有連接。當(dāng)某個(gè)連接有新的數(shù)據(jù)可以處理時(shí)慨飘,操作系統(tǒng)通知應(yīng)用程序确憨,線程從阻塞狀態(tài)返回,開始進(jìn)行業(yè)務(wù)處理
基于線程池復(fù)用線程資源:不必再為每個(gè)連接創(chuàng)建線程瓤的,將連接完成后的業(yè)務(wù)處理任務(wù)分配給線程進(jìn)行處理休弃,一個(gè)線程可以處理多個(gè)連接的業(yè)務(wù)。
總體來說圈膏,Reactor模式有點(diǎn)類似事件驅(qū)動(dòng)模式塔猾。在事件驅(qū)動(dòng)模式中,當(dāng)有事件觸發(fā)時(shí)稽坤,事件源會(huì)將事件分發(fā)到Handler(處理器)丈甸,由Handler負(fù)責(zé)事件處理。Reactor模式中的反應(yīng)器角色類似于事件驅(qū)動(dòng)模式中的事件分發(fā)器(Dispatcher)角色尿褪。
具體來說睦擂,在Reactor模式中有Reactor和Handler兩個(gè)重要的組件:
- Reactor:負(fù)責(zé)查詢IO事件,當(dāng)檢測(cè)到一個(gè)IO事件時(shí)將其發(fā)送給相應(yīng)的Handler處理器去處理杖玲。其中顿仇,IO事件就是NIO中選擇器查詢出來的通道IO事件。
- Handler:與IO事件(或者選擇鍵)綁定摆马,負(fù)責(zé)IO事件的處理臼闻,完成真正的連接建立、通道的讀取今膊、處理業(yè)務(wù)邏輯些阅、負(fù)責(zé)將結(jié)果寫到通道等。
從Reactor的代碼實(shí)現(xiàn)上來看斑唬,實(shí)現(xiàn)Reactor模式需要實(shí)現(xiàn)以下幾個(gè)類:
- EventHandler:事件處理器,可以根據(jù)事件的不同狀態(tài)創(chuàng)建處理不同狀態(tài)的處理器黎泣。
- Handler:可以理解為事件恕刘,在網(wǎng)絡(luò)編程中就是一個(gè)Socket,在數(shù)據(jù)庫操作中就是一個(gè)DBConnection抒倚。
- InitiationDispatcher:用于管理EventHandler褐着,分發(fā)event的容器,也是一個(gè)事件處理調(diào)度器托呕,Tomcat的Dispatcher就是一個(gè)很好的實(shí)現(xiàn),用于接收到網(wǎng)絡(luò)請(qǐng)求后進(jìn)行第一步的任務(wù)分發(fā)景描,分發(fā)給相應(yīng)的處理器去異步處理司顿,來保證吞吐量。
- Demultiplexer:阻塞等待一系列的Handle中的事件到來斟赚,如果阻塞等待返回,即表示在返回的Handler中可以不阻塞的執(zhí)行返回的事件類型差油。這個(gè)模塊一般使用操作系統(tǒng)的select來實(shí)現(xiàn)拗军。在Java NIO中用Selector來封裝,當(dāng)Selector.select()返回時(shí)蓄喇,可以調(diào)用Selector的selectedKeys()方法獲取Set发侵,一個(gè)SelectionKey表達(dá)一個(gè)有事件發(fā)生的Channel以及該Channel上的事件類型。
接下來妆偏,我們便從具體的常見來一一探討一下Reactor模式下的各種線程模型刃鳄。
從一定意義上來說, 基于Reactor模式的Reactor模型是非阻塞I/O模型钱骂。
2.0. 單Reactor單線程模型
單Reactor單線程模型主要是指將服務(wù)端的整個(gè)處理事件分為若干個(gè)事件叔锐,Reactor 通過事件檢測(cè)機(jī)制把若干個(gè)事件Handler分發(fā)給不同的處理器去處理。簡(jiǎn)單來說罐柳,Reactor和Handle都放入一個(gè)線程中執(zhí)行掌腰。
在實(shí)際工作中,若干個(gè)客戶端連接訪問服務(wù)端张吉,假如會(huì)有接收事件(Accept Event)齿梁,讀事件(Read Event),寫事件(Write Event)肮蛹,以及執(zhí)行事件(Process Event)等勺择,其中,:
- Reactor 模型則把這些事件都分發(fā)到各自的處理器伦忠。
- 整個(gè)過程省核,只要有等待處理的事件存在,Reactor 線程模型不斷往后續(xù)執(zhí)行昆码,而且不會(huì)阻塞气忠,所以效率很高。
由此可見赋咽,單Reactor單線程模型具有簡(jiǎn)單旧噪,沒有多線程,沒有進(jìn)程通信脓匿。但是從性能上來說淘钟,無法發(fā)揮多核的極致,一個(gè)Handler卡死陪毡,導(dǎo)致當(dāng)前進(jìn)程無法使用米母,IO和CPU不匹配勾扭。
在Java領(lǐng)域中,對(duì)于一個(gè)單Reactor單線程模型的實(shí)現(xiàn)铁瞒,主要需用到SelectionKey(選擇鍵)的幾個(gè)重要的成員方法:
- void attach(Object o):將對(duì)象附加到選擇鍵妙色。可以將任何Java POJO對(duì)象作為附件添加到SelectionKey實(shí)例精拟。
- Object attachment():從選擇鍵獲取附加對(duì)象燎斩。與attach(Object o)是配套使用的,其作用是取出之前通過attach(Object o)方法添加到SelectionKey實(shí)例的附加對(duì)象蜂绎。這個(gè)方法同樣非常重要栅表,當(dāng)IO事件發(fā)生時(shí),選擇鍵將被select方法查詢出來师枣,可以直接將選擇鍵的附件對(duì)象取出怪瓶。
因此,在Reactor模式實(shí)現(xiàn)中践美,通過attachment()方法所取出的是之前通過attach(Object o)方法綁定的Handler實(shí)例洗贰,然后通過該Handler實(shí)例完成相應(yīng)的傳輸處理。
綜上所述陨倡,在Reactor模式中敛滋,需要將attach和attachment結(jié)合使用:
- 在選擇鍵注冊(cè)完成之后調(diào)用attach()方法,將Handler實(shí)例綁定到選擇鍵兴革。
- 當(dāng)IO事件發(fā)生時(shí)調(diào)用attachment()方法绎晃,可以從選擇鍵取出Handler實(shí)例,將事件分發(fā)到Handler處理器中完成業(yè)務(wù)處理杂曲。
從一定意義上來說庶艾,單Reactor單線程模型是基于單線程的Reactor模式。
2.1. 單Reactor多線程模型
單Reactor多線程模型是指采用多線程機(jī)制擎勘,將服務(wù)端的整個(gè)處理事件分為若干個(gè)事件咱揍,Reactor 通過事件檢測(cè)機(jī)制把若干個(gè)事件Handler分發(fā)給不同的處理器去處理。
單Reactor多線程模型是基于單線程的Reactor模式的結(jié)構(gòu)棚饵,將其利用線程池機(jī)制改進(jìn)多線程模式煤裙。
相當(dāng)于,Reactor對(duì)于接收事件(Accept Event)噪漾,讀事件(Read Event)积暖,寫事件(Write Event),以及執(zhí)行事件(Process Event)等分發(fā)到各自的處理器時(shí):
- 首先怪与,對(duì)于耗時(shí)的任務(wù)引入線程池機(jī)制,事件處理器自己不執(zhí)行任務(wù)缅疟,而是交給線程池來托管分别,避免了耗時(shí)的操作遍愿。
- 其次,雖然Reactor只有一個(gè)線程耘斩,但是也保證了Reactor的高效沼填。
在Java領(lǐng)域中,對(duì)于一個(gè)單Reactor多線程模型的實(shí)現(xiàn)括授,主要可以從升級(jí)Handler和升級(jí)Reactor來改進(jìn):
- 升級(jí)Handler:既要使用多線程坞笙,又要盡可能高效率,則可以考慮使用線程池荚虚。
- 升級(jí)Reactor:可以考慮引入多個(gè)Selector(選擇器)薛夜,提升選擇大量通道的能力。
總體來說版述,多線程版本的Reactor模式大致如下:
- 將負(fù)責(zé)數(shù)據(jù)傳輸處理的IOHandler處理器的執(zhí)行放入獨(dú)立的線程池中梯澜。這樣,業(yè)務(wù)處理線程與負(fù)責(zé)新連接監(jiān)聽的反應(yīng)器線程就能相互隔離渴析,避免服務(wù)器的連接監(jiān)聽受到阻塞晚伙。
- 如果服務(wù)器為多核的CPU,可以將反應(yīng)器線程拆分為多個(gè)子反應(yīng)器(SubReactor)線程俭茧;同時(shí)咆疗,引入多個(gè)選擇器,并且為每一個(gè)SubReactor引入一個(gè)線程母债,一個(gè)線程負(fù)責(zé)一個(gè)選擇器的事件輪詢午磁。這樣充分釋放了系統(tǒng)資源的能力,也大大提升了反應(yīng)器管理大量連接或者監(jiān)聽大量傳輸通道的能力场斑。
由此可見漓踢,單Reactor單線程模型具有充分利用的CPU的特點(diǎn),但是進(jìn)程通信漏隐,復(fù)雜喧半,Reactor承放了太多業(yè)務(wù),高并發(fā)下可能成為性能瓶頸青责。
從一定意義上來說挺据,單Reactor多線程模型是基于多線程的Reactor模式。
2.2. 主從Reactor多線程模型
主從Reactor多線程模型采用多個(gè)Reactor 的機(jī)制脖隶,將服務(wù)端的整個(gè)處理事件分為若干個(gè)事件扁耐,Reactor 通過事件檢測(cè)機(jī)制把若干個(gè)事件Handler分發(fā)給不同的處理器去處理。每一個(gè)Reactor對(duì)應(yīng)著一個(gè)線程产阱。
采用多個(gè)Reactor實(shí)例的機(jī)制:
-主Reactor:負(fù)責(zé)建立連接婉称,建立連接后的句柄丟給從Reactor。
-從Reactor: 負(fù)責(zé)監(jiān)聽所有事件進(jìn)行處理。
相當(dāng)于王暗,Reactor對(duì)于接收事件(Accept Event)悔据,讀事件(Read Event),寫事件(Write Event)俗壹,以及執(zhí)行事件(Process Event)等分發(fā)到各自的處理器時(shí):
- 由于接收事件是針對(duì)于服務(wù)器端而言的科汗,連接接收的工作統(tǒng)一由連接處理器完成,則連接處理器把接收到的客戶端連接均勻分配到所有的實(shí)例中去绷雏。
- 每一個(gè)Reactor 實(shí)例負(fù)責(zé)處理分配到該Reactor 實(shí)例的客戶端連接头滔,完成連接時(shí)的讀寫操作和其他邏輯操作。
由此可見涎显,主從Reactor多線程模型中Reactor實(shí)例職責(zé)分工明確坤检,具有一定分?jǐn)倝毫Φ男埽覀兂R奛ginx/Netty/Memcached等就是采用這中模型棺禾。
從一定意義上來說缀蹄,主從Reactor多線程模型是基于多實(shí)例的Reactor模式。
2. 基于Proactor模式的Proactor模型
Proactor 模型是指在事件驅(qū)動(dòng)的思想上膘婶,基于Proactor 的工作模式而設(shè)計(jì)的異步I/O模型(AIO 模型)缺前,一定程度上來說,可以說是被動(dòng)模式I/O模型悬襟。
無論是 Reactor衅码,還是 Proactor,都是一種基于事件分發(fā)的網(wǎng)絡(luò)編程模式脊岳,區(qū)別在于 Reactor 模式是基于「待完成」的 I/O 事件逝段,而 Proactor 模式則是基于「已完成」的 I/O 事件。
相對(duì)于Reactor來說割捅,Proactor 模型處理讀取操作的主要流程:
- 應(yīng)用程序初始化一個(gè)異步讀取操作奶躯,然后注冊(cè)相應(yīng)的事件處理器,此時(shí)事件處理器不關(guān)注讀取就緒事件亿驾,而是關(guān)注讀取完成事件嘹黔。
- 事件分離器等待讀取操作完成事件。
- 在事件分離器等待讀取操作完成的時(shí)候莫瞬,操作系統(tǒng)調(diào)用內(nèi)核線程完成讀取操作儡蔓,并將讀取的內(nèi)容放入用戶傳遞過來的緩存區(qū)中。
- 事件分離器捕獲到讀取完成事件后疼邀,激活應(yīng)用程序注冊(cè)的事件處理器喂江,事件處理器直接從緩存區(qū)讀取數(shù)據(jù),而不需要進(jìn)行實(shí)際的讀取操作旁振。
由此可見获询,Proactor中寫入操作和讀取操作基本一致涨岁,只不過監(jiān)聽的事件是寫入完成事件而已。
在Java領(lǐng)域中筐付,異步IO(AIO)是在Java JDK 7 之后引入的卵惦,都是操作系統(tǒng)負(fù)責(zé)將數(shù)據(jù)讀寫到應(yīng)用傳遞進(jìn)來的緩沖區(qū)供應(yīng)用程序操作。
其中瓦戚,從對(duì)于Proactor模式的設(shè)計(jì)來看,Proactor 模式的工作流程:
- Proactor Initiator: 負(fù)責(zé)創(chuàng)建 Proactor 和 Handler 對(duì)象丛塌,并將 Proactor 和 Handler 都通過 Asynchronous Operation Processor 注冊(cè)到內(nèi)核较解。
- Asynchronous Operation Processor :負(fù)責(zé)處理注冊(cè)請(qǐng)求,并處理 I/O 操作赴邻。
- Asynchronous Operation Processor :完成 I/O 操作后通知 Proactor印衔。
- Proactor :根據(jù)不同的事件類型回調(diào)不同的 Handler 進(jìn)行業(yè)務(wù)處理。
- Handler: 完成業(yè)務(wù)處理姥敛,其中是通過CompletionHandler表示完成后處理器奸焙。
從一定意義上來說, 基于Proactor模式的Proactor模型是異步IO彤敛。
3. 基于Promise模式的Promise模型
Promise模型是基于Promise異步編程模式与帆,客戶端代碼調(diào)用某個(gè)異步方法所得到的返回值僅是一個(gè)憑據(jù)對(duì)象,憑借該對(duì)象墨榄,客戶端代碼可以獲取異步方法相應(yīng)的真正任務(wù)的執(zhí)行結(jié)果的一種模型玄糟。
Promise 模式是開始一個(gè)任務(wù)的執(zhí)行,并得到一個(gè)用于獲取該任務(wù)執(zhí)行結(jié)果的憑據(jù)對(duì)象袄秩,而不必等待該任務(wù)執(zhí)行完畢就可以繼續(xù)執(zhí)行其他操作阵翎。
從Promise 模式的工作機(jī)制來看,主要如下:
- 當(dāng)我們開始一個(gè)任務(wù)的執(zhí)行之剧,并得到一個(gè)用于獲取該任務(wù)執(zhí)行結(jié)果的憑據(jù)對(duì)象郭卫,而不必等待該任務(wù)執(zhí)行完畢就可以繼續(xù)執(zhí)行其他操作。
- 等到我們需要該任務(wù)的執(zhí)行結(jié)果時(shí)背稼,再調(diào)用憑據(jù)對(duì)象的相關(guān)方法來獲取贰军。
由此可以確定的是,Promise 模式既發(fā)揮了異步編程的優(yōu)勢(shì)——增加系統(tǒng)的并發(fā)性雇庙,減少不必要的等待谓形,又保持了同步編程的簡(jiǎn)單性。
從Promise 模式技術(shù)實(shí)現(xiàn)來說疆前,主要職責(zé)角色如下:
- Promisor:負(fù)責(zé)對(duì)外暴露可以返回 Promise 對(duì)象的異步方法寒跳,并啟動(dòng)異步任務(wù)的執(zhí)行,主要利用compute方法啟動(dòng)異步任務(wù)的執(zhí)行竹椒,并返回用于獲取異步任務(wù)執(zhí)行結(jié)果的憑據(jù)對(duì)象童太。
- Promise :負(fù)責(zé)包裝異步任務(wù)處理結(jié)果的憑據(jù)對(duì)象。負(fù)責(zé)檢測(cè)異步任務(wù)是否處理完畢、返回和存儲(chǔ)異步任務(wù)處理結(jié)果书释。
- Result :負(fù)責(zé)表示異步任務(wù)處理結(jié)果翘贮。具體類型由應(yīng)用決定。
- TaskExecutor:負(fù)責(zé)真正執(zhí)行異步任務(wù)所代表的計(jì)算爆惧,并將其計(jì)算結(jié)果設(shè)置到相應(yīng)的 Promise 實(shí)例對(duì)象狸页。
在Java領(lǐng)域中,最典型的就是基于Future模型實(shí)現(xiàn)的Executor和FutureTask扯再。
由于Future模型存在一定的局限性芍耘,在JDK 1.8 之后,對(duì)Future的擴(kuò)展和增強(qiáng)實(shí)現(xiàn)又新增了一個(gè)CompletableFuture熄阻。
當(dāng)然斋竞,Promise模式在前端技術(shù)JavaScript中Promise有具體的體現(xiàn),而且隨著前端技術(shù)的發(fā)展日趨成熟秃殉,對(duì)于這種模式的運(yùn)用早已日臻化境坝初。
寫在最后
在Java領(lǐng)域中,Java領(lǐng)域中的線程主要分為Java層線程(Java Thread) ,JVM層線程(JVM Thread),操作系統(tǒng)層線程(Kernel Thread)钾军。
從Java線程映射類型來看鳄袍,主要有線程一對(duì)一(1:1)映射,線程多對(duì)多(M:1)映射巧颈,線程多對(duì)多(M:N)映射等關(guān)系畦木。
因此,Java 領(lǐng)域中的線程映射模型主要有內(nèi)核級(jí)線程模型(Kernel-Level Thread 砸泛,KLT)十籍、應(yīng)用級(jí)線程模型(Application-Level Thread ,ALT)唇礁、混合兩級(jí)線程模型(Mixture-Level Thread 勾栗,MLT)等3種模型。
在Java領(lǐng)域中盏筐,我們對(duì)照線程概念(單線程和多線程)來說围俘,可以分為Java 線程-阻塞I/O模型和Java 線程-非阻塞I/O模型兩種。其中琢融,
- Java 線程-阻塞I/O模型: 主要可以分為單線程阻塞I/O模型和多線程阻塞I/O模型界牡。
- Java 線程-非阻塞I/O模型:主要可以分為應(yīng)用層I/O多路復(fù)用模型和內(nèi)核層I/O多路復(fù)用模型,以及內(nèi)核回調(diào)事件驅(qū)動(dòng)I/O模型漾抬。
特別指出宿亡,在Java領(lǐng)域中,非阻塞I/O的實(shí)現(xiàn)完全是基于操作系統(tǒng)內(nèi)核的非阻塞I/O纳令,JDK會(huì)依據(jù)操作系統(tǒng)內(nèi)核支持的非阻塞I/O方式來幫助我們選擇實(shí)現(xiàn)方式挽荠。
綜上所述克胳,在Java領(lǐng)域中,并發(fā)編程中的線程機(jī)制以及多線程的控制圈匆,在實(shí)際開發(fā)過程中漠另,需要依據(jù)實(shí)際業(yè)務(wù)場(chǎng)景來考慮和衡量,這需要我們對(duì)其有更深的研究跃赚,才可以得心應(yīng)手笆搓。
在討論編程模型的時(shí)候,我們提到了像基于Promise模式和基于Thread Pool 模式的這樣的設(shè)計(jì)模式的概念来累,這也是一個(gè)我們比較容易忽略的概念砚作,如果有興趣的話,可以自行進(jìn)行查詢相關(guān)資料進(jìn)行了解嘹锁。
最后,祝福大家在Java并發(fā)編程的“看山是山着裹,看山不是山领猾,看山還是山”的修行之路上,“撥開云霧見天日骇扇,守得云開見月明”摔竿,早日達(dá)到有跡可循到有跡可尋的目標(biāo)!
版權(quán)聲明:本文為博主原創(chuàng)文章少孝,遵循相關(guān)版權(quán)協(xié)議继低,如若轉(zhuǎn)載或者分享請(qǐng)附上原文出處鏈接和鏈接來源。