Java 并發(fā)編程解析 | 如何正確理解Java領(lǐng)域中的多線程模型憔狞,主要用來解決什么問題?

蒼穹之邊彰阴,浩瀚之摯瘾敢,眰恦之美; 悟心悟性尿这,善始善終簇抵,惟善惟道! —— 朝槿《朝槿兮年說》

Navigation-Picture

寫在開頭

Header-Picture

我國宋代禪宗大師青原行思在《三重境界》中有這樣一句話:“ 參禪之初射众,看山是山碟摆,看水是水;禪有悟時(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ì)算公式:

vJgupF.png

就是我們熟知的阿姆達(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)。

Content-Picture

對(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種模型诗越。

Mapperd-Picture

從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ù)量不受限制侥祭。

MLT-Picture

對(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ù)。

vGAf8x.png

在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è)客戶端的訪問弦追。

v8QD5F.png

單線程阻塞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)求訪問。

v8QcvR.png

多線程阻塞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)行讀寫群叶。

v8lCxs.png

應(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ì)象悯嗓。

v8lCxs.png

內(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ī)制桶蝎。

v8lKz9.png

不論是內(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)用程序操作。

v8TSDe.png

對(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模型华蜒。

v806u8.png

在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模型把敢。

v8lURH.png

對(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í)行掌腰。

v8lGdK.png

在實(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ā)給不同的處理器去處理。

v8l8Z6.png

單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è)線程产阱。

v8l1qx.png

采用多個(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模型悬襟。

v8lJIO.png

無論是 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 模式的工作流程:

v84Emd.png
  • 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é)果的一種模型玄糟。

vYLMQA.png

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)用早已日臻化境坝初。

寫在最后

Footer-Picture

在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)附上原文出處鏈接和鏈接來源。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末稍走,一起剝皮案震驚了整個(gè)濱河市袁翁,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌婿脸,老刑警劉巖粱胜,帶你破解...
    沈念sama閱讀 212,542評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異狐树,居然都是意外死亡焙压,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門抑钟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來涯曲,“玉大人,你說我怎么就攤上這事在塔』眉” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵心俗,是天一觀的道長(zhǎng)傲武。 經(jīng)常有香客問我蓉驹,道長(zhǎng),這世上最難降的妖魔是什么揪利? 我笑而不...
    開封第一講書人閱讀 56,682評(píng)論 1 284
  • 正文 為了忘掉前任态兴,我火速辦了婚禮,結(jié)果婚禮上疟位,老公的妹妹穿的比我還像新娘瞻润。我一直安慰自己,他們只是感情好甜刻,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,792評(píng)論 6 386
  • 文/花漫 我一把揭開白布绍撞。 她就那樣靜靜地躺著,像睡著了一般得院。 火紅的嫁衣襯著肌膚如雪傻铣。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評(píng)論 1 291
  • 那天祥绞,我揣著相機(jī)與錄音非洲,去河邊找鬼。 笑死蜕径,一個(gè)胖子當(dāng)著我的面吹牛两踏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播兜喻,決...
    沈念sama閱讀 39,107評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼梦染,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了朴皆?” 一聲冷哼從身側(cè)響起帕识,我...
    開封第一講書人閱讀 37,845評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎车荔,沒想到半個(gè)月后渡冻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡忧便,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,612評(píng)論 2 327
  • 正文 我和宋清朗相戀三年族吻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片珠增。...
    茶點(diǎn)故事閱讀 38,747評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡超歌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蒂教,到底是詐尸還是另有隱情巍举,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評(píng)論 4 333
  • 正文 年R本政府宣布凝垛,位于F島的核電站懊悯,受9級(jí)特大地震影響蜓谋,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜炭分,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,072評(píng)論 3 317
  • 文/蒙蒙 一桃焕、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧捧毛,春花似錦观堂、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至而账,卻和暖如春胰坟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背泞辐。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評(píng)論 1 267
  • 我被黑心中介騙來泰國打工腕铸, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人铛碑。 一個(gè)月前我還...
    沈念sama閱讀 46,545評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像虽界,于是被迫代替她去往敵國和親汽烦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,658評(píng)論 2 350

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