4、Java基本功-并發(fā)編程

懦尝、基本概念

1知纷、進程與線程

? ? ? ? 進程是具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合上的一次運行活動,進程是系統(tǒng)進行資源分配和調(diào)度的一個獨立單位导披。線程是進程的一個實體屈扎,是CPU調(diào)度和分派的基本單位,它是比進程更小的能獨立運行的基本單位撩匕。線程自己基本上不擁有系統(tǒng)資源,只擁有一點在運行中必不可少的資源(如程序計數(shù)器墨叛、一組寄存器和棧)止毕,但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源模蜡。

2、并發(fā)和并行

? ? 當(dāng)有多個線程在操作時扁凛,如果系統(tǒng)只有一個CPU忍疾,則它根本不可能真正同時進行一個以上的線程,它只能把CPU運行時間劃分成若干個時間段谨朝,再將時間段分配給各個線程執(zhí)行卤妒,在一個時間段的線程代碼運行時,其他線程處于掛起狀態(tài)字币。這種方式我們稱之為并發(fā)(Concurrent)则披。

? ? 當(dāng)系統(tǒng)有一個以上的CPU時,則線程的操作有可能非并發(fā)洗出。當(dāng)一個CPU執(zhí)行一個線程時士复,另一個CPU可以執(zhí)行另一個線程,兩個線程互不搶占CPU資源翩活,可以同時進行阱洪,這種方式我們稱之為并行(Parallel)。

? ? 區(qū)別:并發(fā)和并行是即相似又有區(qū)別的兩個概念菠镇,并行是指兩個或者多個事件在同一時刻發(fā)生冗荸;而并發(fā)是指兩個或多個事件在同一時間間隔內(nèi)發(fā)生。

3利耍、上下文切換

? ? ? ?CPU是通過時間片分配算法循環(huán)執(zhí)行任務(wù)俏竞,任務(wù)從保存到下次加載就是一次上下文切換,通過vmstat的cs參數(shù)堂竟,可以獲取到每秒上下文切換次數(shù)魂毁,大約1000次。減少上下文的切換在并發(fā)編程中要考慮的出嘹,通常采用三種方法席楚,1)盡量避免使用鎖的方式,采用HASH算法將數(shù)據(jù)分片税稼,不同任務(wù)處理不同的數(shù)據(jù)烦秩;2)盡量采用CAS算法的Atomic包;3)增加協(xié)程郎仆,利用協(xié)程對多任務(wù)進行調(diào)度只祠,完成任務(wù)切換。

4扰肌、鎖

????????鎖(lock)作為用于保護臨界區(qū)(critical sec-tion)的一種機制抛寝,被廣泛應(yīng)用在多線程程序中,比如Java(synchronized,ReentrantLock…)盗舰。

????????重入鎖:就是支持重進入的鎖晶府,它表示該鎖能夠支持一個線程對資源的重復(fù)加鎖。除此之外钻趋,該鎖的還支持獲取鎖時的公平和非公平性選擇川陆。

????????讀寫鎖:在同一時刻可以允許多個讀線程訪問,但是在寫線程訪問時蛮位,所有的讀線程和其他寫線程均被阻塞较沪。讀寫鎖維護了一對鎖,一個讀鎖和一個寫鎖失仁,通過分離讀鎖和寫鎖尸曼,使得并發(fā)性相比一般的排他鎖有了很大提升。

二陶因、Java并發(fā)機制原理

1骡苞、volatile

????????關(guān)鍵字volatile可以用來修飾字段(成員變量),就是告知程序任何對該變量的訪問均需要從共享內(nèi)存中獲取楷扬,而對它的改變必須同步刷新回共享內(nèi)存解幽,它能保證所有線程對變量訪問的可見性。volatile是輕量級的synchronized烘苹,它在多處理器開發(fā)中保證了共享變量的“可見性”躲株。可見性的意思是當(dāng)一個線程修改一個共享變量時镣衡,另外一個線程能讀到這個修改的值霜定。如果volatile變量修飾符使用恰當(dāng)?shù)脑挘萻ynchronized的使用和執(zhí)行成本更低廊鸥,因為它不會引起線程上下文的切換和調(diào)度望浩。

2、synchronized

????????關(guān)鍵字synchronized可以修飾方法或者以同步塊的形式來進行使用惰说,它主要確保多個線程在同一個時刻磨德,只能有一個線程處于方法或者同步塊中,它保證了線程對變量訪問的可見性和排他性吆视。

????????先來看下利用synchronized實現(xiàn)同步的基礎(chǔ):Java中的每一個對象都可以作為鎖典挑。具體表現(xiàn)為以下3種形式。 1)對于普通同步方法啦吧,鎖是當(dāng)前實例對象您觉。 2)對于靜態(tài)同步方法,鎖是當(dāng)前類的Class對象授滓。 3)對于同步方法塊琳水,鎖是Synchonized括號里配置的對象肆糕。當(dāng)一個線程試圖訪問同步代碼塊時,它首先必須得到鎖炫刷,退出或拋出異常時必須釋放鎖亡嫌。那么鎖到底存在哪里呢添祸?鎖里面會存儲什么信息呢?

? ??????Java SE 1.6為了減少獲得鎖和釋放鎖帶來的性能消耗拒名,引入了“偏向鎖”和“輕量級鎖”噩咪,在Java SE 1.6中顾彰,鎖一共有4種狀態(tài),級別從低到高依次是:無鎖狀態(tài)胃碾、偏向鎖狀態(tài)涨享、輕量級鎖狀態(tài)和重量級鎖狀態(tài),這幾個狀態(tài)會隨著競爭情況逐漸升級仆百。鎖可以升級但不能降級厕隧,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略俄周,目的是為了提高獲得鎖和釋放鎖的效率吁讨,

????????使用synchronized關(guān)鍵字將會隱式地獲取鎖,但是它將鎖的獲取和釋放固化了峦朗,也就是先獲取再釋放建丧。當(dāng)然,這種方式簡化了同步的管理波势,可是擴展性沒有顯示的鎖獲取和釋放來的好翎朱。例如,針對一個場景尺铣,手把手進行鎖獲取和釋放拴曲,先獲得鎖A,然后再獲取鎖B凛忿,當(dāng)鎖B獲得后澈灼,釋放鎖A同時獲取鎖C,當(dāng)鎖C獲得后侄非,再釋放B同時獲取鎖D蕉汪,以此類推。這種場景下逞怨,syn-chronized關(guān)鍵字就不那么容易實現(xiàn)了者疤,而使用Lock卻容易許多。

3叠赦、Lock接口

????????鎖是用來控制多個線程訪問共享資源的方式驹马,一般來說革砸,一個鎖能夠防止多個線程同時訪問共享資源(但是有些鎖可以允許多個線程并發(fā)的訪問共享資源,比如讀寫鎖)糯累。在Lock接口出現(xiàn)之前算利,Java程序是靠synchronized關(guān)鍵字實現(xiàn)鎖功能的,而JavaSE 5之后泳姐,并發(fā)包中新增了Lock接口(以及相關(guān)實現(xiàn)類)用來實現(xiàn)鎖功能效拭,它提供了與synchronized關(guān)鍵字類似的同步功能,只是在使用時需要顯式地獲取和釋放鎖胖秒。雖然它缺少了(通過synchronized塊或者方法所提供的)隱式獲取釋放鎖的便捷性缎患,但是卻擁有了鎖獲取與釋放的可操作性、可中斷的獲取鎖以及超時獲取鎖等多種synchronized關(guān)鍵字所不具備的同步特性阎肝。Lock接口的實現(xiàn)基本都是通過聚合了一個同步器的子類來完成線程訪問控制的挤渔。

????????隊列同步器AbstractQueuedSynchronizer(以下簡稱同步器),是用來構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架风题,它使用了一個int成員變量表示同步狀態(tài)判导,通過內(nèi)置的FIFO隊列來完成資源獲取線程的排隊工作,并發(fā)包的作者(Doug Lea)期望它能夠成為實現(xiàn)大部分同步需求的基礎(chǔ)沛硅。JVM中的CAS操作正是利用了處理器提供的CMPXCHG指令實現(xiàn)的眼刃。自旋CAS實現(xiàn)的基本思路就是循環(huán)進行CAS操作直到成功為止。稽鞭,JVM實現(xiàn)鎖的方式都用了循環(huán)CAS鸟整,即當(dāng)一個線程想進入同步塊的時候使用循環(huán)CAS的方式來獲取鎖,當(dāng)它退出同步塊的時候使用循環(huán)CAS釋放鎖朦蕴。同步器是實現(xiàn)鎖(也可以是任意同步組件)的關(guān)鍵篮条,在鎖的實現(xiàn)中聚合同步器,利用同步器實現(xiàn)鎖的語義吩抓∩婕耄可以這樣理解二者之間的關(guān)系:鎖是面向使用者的,它定義了使用者與鎖交互的接口(比如可以允許兩個線程并行訪問)疹娶,隱藏了實現(xiàn)細節(jié)伴栓;同步器面向的是鎖的實現(xiàn)者,它簡化了鎖的實現(xiàn)方式雨饺,屏蔽了同步狀態(tài)管理钳垮、線程的排隊、等待與喚醒等底層操作额港。鎖和同步器很好地隔離了使用者和實現(xiàn)者所需關(guān)注的領(lǐng)域饺窿。

三、JDK并發(fā)類庫

1移斩、ThreadLocal

????????ThreadLocal肚医,即線程變量绢馍,是一個以ThreadLocal對象為鍵、任意對象為值的存儲結(jié)構(gòu)肠套。這個結(jié)構(gòu)被附帶在線程上舰涌,也就是說一個線程可以根據(jù)一個ThreadLocal對象查詢到綁定在這個線程上的一個值。

????????ThreadLocal類在維護變量時你稚,實際使用了當(dāng)前線程(Thread)中的一個叫做ThreadLocalMap的獨立副本瓷耙,每個線程可以獨立修改屬于自己的副本而不會互相影響,從而隔離了線程和線程入宦,避免了線程訪問實例變量發(fā)生沖突的問題哺徊。ThreadLocal本身并不是一個線程室琢,而是通過操作當(dāng)前線程中的一個內(nèi)部變量來達到與其他線程隔離的目的乾闰。之所以取名為ThreadLocal,所期望表達的含義是其操作的對象是線程的一個本地變量盈滴。

????????ThreadLocal模式至少從兩個方面完成了數(shù)據(jù)訪問隔離涯肩,即橫向隔離和縱向隔離。有了橫向和縱向兩種不同的隔離方式巢钓,ThreadLocal模式就能真正地做到線程安全病苗;1)縱向隔離——線程與線程之間的數(shù)據(jù)訪問隔離。這一點由線程的數(shù)據(jù)結(jié)構(gòu)保證症汹。因為每個線程在進行對象訪問時硫朦,訪問的都是各個線程自己的ThreadLocalMap。 2)橫向隔離——同一個線程中背镇,不同的ThreadLocal實例操作的對象之間相互隔離咬展。這一點由ThreadLocalMap在存儲時采用當(dāng)前ThreadLocal的實例作為key來保證。

????????ThreadLocal具有以下特點: 1)ThreadLocalMap變量屬于線程的內(nèi)部屬性瞒斩,不同的線程擁有完全不同的ThreadLocalMap變量破婆。? 2)線程中的ThreadLocalMap變量的值是在ThreadLocal對象進行set或者get操作時創(chuàng)建的。 3)在創(chuàng)建ThreadLocalMap之前胸囱,會首先檢查當(dāng)前線程中的ThreadLocalMap變量是否已經(jīng)存在祷舀,如果不存在則創(chuàng)建一個;如果已經(jīng)存在烹笔,則使用當(dāng)前線程已創(chuàng)建的ThreadLo-calMap裳扯。 4)使用當(dāng)前線程的ThreadLocalMap的關(guān)鍵在于使用當(dāng)前的ThreadLocal的實例作為key進行存儲。

2谤职、ConcurrentHashMap

????????ConcurrentHashMap采用了分拆鎖的思想饰豺,實現(xiàn)使用了一個包含16個鎖的數(shù)組,每一個鎖都守護HashMap的1/16柬帕。假設(shè)Hash值均勻分布哟忍,這將會把對于鎖的請求減少到約為原來的1/16狡门。這項技術(shù)使得ConcurrentHashMap能夠支持16個并發(fā)Writer。當(dāng)多處理器系統(tǒng)的大負荷訪問需要更好的并發(fā)性時锅很,鎖的數(shù)量還可以增加其馏。

? ??????ConcurrentHashMap是由Segment數(shù)組結(jié)構(gòu)和HashEntry數(shù)組結(jié)構(gòu)組成。Segment是一種可重入鎖(ReentrantLock)爆安,在ConcurrentHashMap里扮演鎖的角色叛复;HashEntry則用于存儲鍵值對數(shù)據(jù)。一個ConcurrentHashMap里包含一個Segment數(shù)組扔仓。Segment的結(jié)構(gòu)和HashMap類似褐奥,是一種數(shù)組和鏈表結(jié)構(gòu)。一個Segment里包含一個HashEntry數(shù)組翘簇,每個HashEntry是一個鏈表結(jié)構(gòu)的元素撬码,每個Segment守護著一個HashEntry數(shù)組里的元素,當(dāng)對HashEntry數(shù)組的數(shù)據(jù)進行修改時版保,必須首先獲得與它對應(yīng)的Segment鎖呜笑。

? ??????ConcurrentHashMap常用有3種操作——get操作、put操作和size操作彻犁。Segment的get操作實現(xiàn)非常簡單和高效叫胁。get操作的高效之處在于整個get過程不需要加鎖,除非讀到的值是空才會加鎖重讀汞幢。put方法里需要對共享變量進行寫入操作驼鹅,所以為了線程安全,在操作共享變量時必須加鎖森篷。

3输钩、BlockingQueue

????????阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。這兩個附加的操作支持阻塞的插入和移除方法疾宏。1)支持阻塞的插入方法:意思是當(dāng)隊列滿時张足,隊列會阻塞插入元素的線程,直到隊列不滿坎藐。2)支持阻塞的移除方法:意思是在隊列為空時为牍,獲取元素的線程會等待隊列變?yōu)榉强铡?/p>

????????阻塞隊列常用于生產(chǎn)者和消費者的場景,生產(chǎn)者是向隊列里添加元素的線程岩馍,消費者是從隊列里取元素的線程碉咆。阻塞隊列就是生產(chǎn)者用來存放元素、消費者用來獲取元素的容器蛀恩。

????????JDK 7提供了7個阻塞隊列疫铜,如下。?

????????1)ArrayBlockingQueue:一個由數(shù)組結(jié)構(gòu)組成的有界阻塞隊列双谆。?此隊列按照先進先出(FIFO)的原則對元素進行排序壳咕。

????????2)LinkedBlockingQueue:一個由鏈表結(jié)構(gòu)組成的有界阻塞隊列席揽。?此隊列的默認和最大長度為Integer.MAX_VALUE。此隊列按照先進先出的原則對元素進行排序谓厘。

? ? ? ? 3)PriorityBlockingQueue:一個支持優(yōu)先級排序的無界阻塞隊列幌羞。?默認情況下元素采取自然順序升序排列。

????????4)DelayQueue:一個使用優(yōu)先級隊列實現(xiàn)的無界阻塞隊列竟稳。?隊列使用PriorityQueue來實現(xiàn)属桦。隊列中的元素必須實現(xiàn)Delayed接口,在創(chuàng)建元素時可以指定多久才能從隊列中獲取當(dāng)前元素他爸。只有在延遲期滿時才能從隊列中提取元素聂宾。

? ? ? ? 5)SynchronousQueue:一個不存儲元素的阻塞隊列。?每一個put操作必須等待一個take操作诊笤,否則不能繼續(xù)添加元素系谐。

? ? ? ? 6)LinkedTransferQueue:一個由鏈表結(jié)構(gòu)組成的無界阻塞隊列。?相對于其他阻塞隊列盏混,LinkedTransfer-Queue多了tryTransfer和transfer方法蔚鸥。

? ? ? ? 7)LinkedBlockingDeque:一個由鏈表結(jié)構(gòu)組成的雙向阻塞隊列。所謂雙向隊列指的是可以從隊列的兩端插入和移出元素许赃。雙向隊列因為多了一個操作隊列的入口,在多線程同時入隊時馆类,也就減少了一半的競爭混聊。

4、CountDownLatch

????????CountDownLatch允許一個或多個線程等待其他線程完成操作乾巧。CountDownLatch的構(gòu)造函數(shù)接收一個int類型的參數(shù)作為計數(shù)器句喜,如果你想等待N個點完成,這里就傳入N沟于。當(dāng)我們調(diào)用CountDownLatch的countDown方法時咳胃,N就會減1,CountDownLatch的await方法會阻塞當(dāng)前線程旷太,直到N變成零展懈。由于countDown方法可以用在任何地方,所以這里說的N個點供璧,可以是N個線程存崖,也可以是1個線程里的N個執(zhí)行步驟。用在多個線程時睡毒,只需要把這個CountDownLatch的引用傳遞到線程里即可来惧。

???????CyclicBarrier和CountDownLatch不同,CyclicBarrier是當(dāng)await的數(shù)量到達了設(shè)定的數(shù)量后演顾,才繼續(xù)往下執(zhí)行供搀。CyclicBarrier可以用于多線程計算數(shù)據(jù)隅居,最后合并計算結(jié)果的場景。?CountDownLatch的計數(shù)器只能使用一次葛虐,而CyclicBarrier的計數(shù)器可以使用reset()方法重置军浆。所以CyclicBarrier能處理更為復(fù)雜的業(yè)務(wù)場景。例如挡闰,如果計算發(fā)生錯誤乒融,可以重置計數(shù)器,并讓線程重新執(zhí)行一次摄悯。

5赞季、FutureTask

????????FutureTask可用于要異步獲取執(zhí)行結(jié)果或取消執(zhí)行任務(wù)的場景,通過傳入Runnable或Callable的任務(wù)給FutureTask奢驯,直接調(diào)用其run方法或放入線程池執(zhí)行申钩,之后可在外部通過FutureTask的get異步獲取執(zhí)行結(jié)果。FutureTask可以確保即使調(diào)用了多次run方法瘪阁,它都只會執(zhí)行一次Runnable或Callable任務(wù)撒遣,或者通過cancel取消FutureTask的執(zhí)行等」芏澹可以把FutureTask交給Executor執(zhí)行义黎;也可以通過ExecutorService.submit方法返回一個FutureTask,然后執(zhí)行FutureTask.get()方法或FutureTask.cancel方法豁跑。除此以外廉涕,還可以單獨使用FutureTask。FutureTask的實現(xiàn)基于AbstractQueuedSynchronizer(簡稱為AQS)艇拍。

????????Future接口可以構(gòu)建異步應(yīng)用狐蜕,但依然有其局限性。它很難直接表述多個Future 結(jié)果之間的依賴性卸夕。實際開發(fā)中层释,我們經(jīng)常需要達成以下目的:1)將兩個異步計算合并為一個——這兩個異步計算之間相互獨立,同時第二個又依賴于第一個的結(jié)果快集。2)等待 Future 集合中的所有任務(wù)都完成贡羔。3)僅等待 Future集合中最快結(jié)束的任務(wù)完成(有可能因為它們試圖通過不同的方式計算同一個值),并返回它的結(jié)果碍讨。4)通過編程方式完成一個Future任務(wù)的執(zhí)行(即以手工設(shè)定異步操作結(jié)果的方式)治力。5)應(yīng)對 Future 的完成事件(即當(dāng) Future 的完成事件發(fā)生時會收到通知,并能使用 Future 計算的結(jié)果進行下一步的操作勃黍,不只是簡單地阻塞等待操作的結(jié)果)

????????JDK1.8才新加入的的CompletableFuture類將使得這些成為可能宵统。實現(xiàn)了Future<T>, CompletionStage<T>兩個接口。當(dāng)一個Future可能需要顯示地完成時,使用CompletionStage接口去支持完成時觸發(fā)的函數(shù)和操作马澈。CompletableFuture配合流式編程瓢省,速度較快,避免了Future引起的CPU高速輪詢痊班、耗資源的問題勤婚,推薦使用。

6涤伐、ThreadPoolExecutor

????????從JDK 5開始馒胆,把工作單元與執(zhí)行機制分離開來。工作單元包括Runnable和Callable凝果,而執(zhí)行機制由Executor框架提供祝迂。Executor框架主要由3大部分組成如下。 1)任務(wù)器净。包括被執(zhí)行任務(wù)需要實現(xiàn)的接口:Runnable接口或Callable接口型雳。2)任務(wù)的執(zhí)行。包括任務(wù)執(zhí)行機制的核心接口Executor山害,以及繼承自Executor的ExecutorService接口纠俭。3)異步計算的結(jié)果。包括接口Future和實現(xiàn)Future接口的FutureTask類浪慌。

? ??????ThreadPoolExecutor是線程池的核心實現(xiàn)類冤荆,用來執(zhí)行被提交的任務(wù)【焐洌可以通過new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,milliseconds,runnableTaskQueue, handler)創(chuàng)建1個線程池匙赞;傳入?yún)?shù)含義

? ? ????1)corePoolSize(線程池的基本大小):當(dāng)提交一個任務(wù)到線程池時妖碉,線程池會創(chuàng)建一個線程來執(zhí)行任務(wù),即使其他空閑的基本線程能夠執(zhí)行新任務(wù)也會創(chuàng)建線程芥被,等到需要執(zhí)行的任務(wù)數(shù)大于線程池基本大小時就不再創(chuàng)建欧宜。如果調(diào)用了線程池的prestartAllCoreThreads()方法,線程池會提前創(chuàng)建并啟動所有基本線程拴魄。

????????2)maximumPoolSize(線程池最大數(shù)量):線程池允許創(chuàng)建的最大線程數(shù)冗茸。如果隊列滿了,并且已創(chuàng)建的線程數(shù)小于最大線程數(shù)匹中,則線程池會再創(chuàng)建新的線程執(zhí)行任務(wù)夏漱。值得注意的是,如果使用了無界的任務(wù)隊列這個參數(shù)就沒什么效果顶捷。

? ? ? ? 3)runnableTaskQueue(任務(wù)隊列):用于保存等待執(zhí)行的任務(wù)的阻塞隊列挂绰。可以選擇以下幾個阻塞隊列服赎。ArrayBlockingQueue:是一個基于數(shù)組結(jié)構(gòu)的有界阻塞隊列葵蒂,此隊列按FIFO(先進先出)原則對元素進行排序交播。LinkedBlockingQueue:一個基于鏈表結(jié)構(gòu)的阻塞隊列,此隊列按FIFO排序元素践付,吞吐量通常要高于ArrayBlockingQueue秦士。靜態(tài)工廠方法Executors.newFixedThreadPool()使用了這個隊列。SynchronousQueue:一個不存儲元素的阻塞隊列永高。每個插入操作必須等到另一個線程調(diào)用移除操作隧土,否則插入操作一直處于阻塞狀態(tài),吞吐量通常要高于LinkedBlockingQueue命爬,靜態(tài)工廠方法Executors.newCachedThread-Pool使用了這個隊列曹傀。PriorityBlockingQueue:一個具有優(yōu)先級的無限阻塞隊列。

? ? ? ? 4)ThreadFactory:用于設(shè)置創(chuàng)建線程的工廠遇骑,可以通過線程工廠給每個創(chuàng)建出來的線程設(shè)置更有意義的名字卖毁。

? ? ? ? 5)RejectedExecutionHandler(飽和策略):當(dāng)隊列和線程池都滿了,說明線程池處于飽和狀態(tài)落萎,那么必須采取一種策略處理提交的新任務(wù)亥啦。這個策略默認情況下是AbortPolicy,表示無法處理新任務(wù)時拋出異常练链。

? ? ? ? 6)keepAliveTime(線程活動保持時間):線程池的工作線程空閑后翔脱,保持存活的時間。所以媒鼓,如果任務(wù)很多届吁,并且每個任務(wù)執(zhí)行的時間比較短,可以調(diào)大時間绿鸣,提高線程的利用率疚沐。

? ? ? ? 7)TimeUnit(線程活動保持時間的單位):可選的單位有天(DAYS)、小時(HOURS)潮模、分鐘(MINUTES)亮蛔、毫秒(MILLISECONDS)、微秒(MICROSECONDS擎厢,千分之一毫秒)和納秒(NANOSECONDS究流,千分之一微秒)。

????????Executors提供了一些方便創(chuàng)建ThreadPoolExecutor的方法动遭,主要有以下幾個芬探;

????????1)newFixedThreadPool(int),創(chuàng)建固定大小的線程池厘惦,線程keepAliveTime為0偷仿,默認情況下,ThreadPoolExecutor中啟動的corePoolSize數(shù)量的線程啟動后就一直運行,并不會由于keepAliveTime時間到達后仍沒有任務(wù)需要執(zhí)行就退出炎疆。緩沖任務(wù)的隊列為LinkedBlock-ingQueue卡骂,大小為整型的最大數(shù)。當(dāng)使用此線程池時形入,在同時執(zhí)行的task數(shù)量超過傳入的線程池大小值后全跨,將會放入Linked-BlockingQueue,在LinkedBlockingQueue中的task需要等待線程空閑后來執(zhí)行亿遂,當(dāng)放入LinkedBlockingQueue中的task超過整型最大數(shù)時浓若,拋出RejectedExecutionException。

????????2)newSingleThreadExecutor()蛇数,相當(dāng)于創(chuàng)建大小為1單位的固定線程池挪钓,當(dāng)使用此線程池時,同時執(zhí)行的task只有1個耳舅,其他task都在LinkedBlockingQueue中碌上。

????????3)newCachedThreadPool(),創(chuàng)建corePoolSize為0浦徊,最大線程數(shù)為整型的最大數(shù)馏予,線程keepAliveTime為1分鐘,緩存任務(wù)的隊列為SynchronousQueue的線程池盔性。在使用時霞丧,放入線程池的task都會復(fù)用線程或啟動新線程來執(zhí)行,直到啟動的線程數(shù)達到整型最大數(shù)后拋出RejectedExecutionException冕香,啟動后的線程存活時間為1分鐘蛹尝。

????????4)newScheduledThreadPool(int)創(chuàng)建corePoolSize為傳入?yún)?shù),最大線程數(shù)為整型的最大數(shù)悉尾,線程keepAliveTime為0突那,緩存任務(wù)的隊列為DelayedWorkQueue的線程池。在實際業(yè)務(wù)中构眯,通常會有一些需要定時或延遲執(zhí)行的任務(wù)陨收,而對于分布式Java應(yīng)用而言,更為典型的則是在異步操作時需要超時回調(diào)的場景鸵赖。這種情況下Sched-uledThreadPoolExecutor是不錯的選擇。

????????可以使用兩個方法向線程池提交任務(wù)拄衰,分別為execute()和submit()方法它褪。execute()方法用于提交不需要返回值的任務(wù),所以無法判斷任務(wù)是否被線程池執(zhí)行成功翘悉。submit()方法用于提交需要返回值的任務(wù)茫打。線程池會返回一個future類型的對象,通過這個future對象可以判斷任務(wù)是否執(zhí)行成功,并且可以通過future的get()方法來獲取返回值老赤,get()方法會阻塞當(dāng)前線程直到任務(wù)完成轮洋,而使用get(long timeout,TimeUnit unit)方法則會阻塞當(dāng)前線程一段時間后立即返回抬旺,這時候有可能任務(wù)沒有執(zhí)行完弊予。

? ??????可以通過調(diào)用線程池的shutdown或shutdownNow方法來關(guān)閉線程池。它們的原理是遍歷線程池中的工作線程开财,然后逐個調(diào)用線程的interrupt方法來中斷線程汉柒,所以無法響應(yīng)中斷的任務(wù)可能永遠無法終止。但是它們存在一定的區(qū)別责鳍,shutdownNow首先將線程池的狀態(tài)設(shè)置成STOP碾褂,然后嘗試停止所有的正在執(zhí)行或暫停任務(wù)的線程,并返回等待執(zhí)行任務(wù)的列表历葛,而shutdown只是將線程池的狀態(tài)設(shè)置成SHUTDOWN狀態(tài)正塌,然后中斷所有沒有正在執(zhí)行任務(wù)的線程。通常調(diào)用shutdown方法來關(guān)閉線程池恤溶,如果任務(wù)不一定要執(zhí)行完乓诽,則可以調(diào)用shutdownNow方法。

四宏娄、JAVA開源框架Netty

1问裕、概述

????????Netty是由JBOSS提供的一個java開源框架。提供異步的孵坚、事件驅(qū)動的網(wǎng)絡(luò)應(yīng)用程序框架和工具粮宛,用以快速開發(fā)高性能、高可靠性的網(wǎng)絡(luò)服務(wù)器和客戶端程序卖宠。

????????Netty 對 JDK 自帶的 NIO 的 API 進行封裝巍杈,簡化了NIO 的類庫和API,提高了可靠性扛伍,簡化了工作量筷畦,解決了客戶端面臨斷連重連、網(wǎng)絡(luò)閃斷刺洒、半包讀寫鳖宾、失敗緩存、網(wǎng)絡(luò)擁塞和異常碼流的處理等問題逆航,避免了原生態(tài)NIO中Epoll Bug導(dǎo)致 Selector 空輪詢的問題鼎文。主要特點有:

????1)設(shè)計優(yōu)雅,適用于各種傳輸類型的統(tǒng)一 API 阻塞和非阻塞 Socket因俐;基于靈活且可擴展的事件模型拇惋,可以清晰地分離關(guān)注點周偎;高度可定制的線程模型 - 單線程,一個或多個線程池撑帖;真正的無連接數(shù)據(jù)報套接字支持(自 3.1 起)蓉坎。

? ? 2)使用方便,詳細記錄的 Javadoc胡嘿,用戶指南和示例蛉艾;沒有其他依賴項,JDK 5(Netty 3.x)或 6(Netty 4.x)就足夠了灶平。

????3)高性能伺通,吞吐量更高,延遲更低逢享;減少資源消耗罐监;最小化不必要的內(nèi)存復(fù)制。

????4)安全瞒爬,完整的 SSL/TLS 和 StartTLS 支持弓柱。

????5)社區(qū)活躍,不斷更新侧但,社區(qū)活躍矢空,版本迭代周期短,發(fā)現(xiàn)的 Bug 可以被及時修復(fù)禀横,同時屁药,更多的新功能會被加入。

????????Netty 作為高性能的基礎(chǔ)通信組件柏锄,它本身提供了 TCP/UDP 和 HTTP 協(xié)議棧酿箭。 非常方便定制和開發(fā)私有協(xié)議棧。典型的應(yīng)用有:阿里分布式服務(wù)框架 Dubbo 的 RPC 框架使用 Dubbo 協(xié)議進行節(jié)點間通信趾娃,Dubbo 協(xié)議默認使用 Netty 作為基礎(chǔ)通信組件缭嫡,用于實現(xiàn)各進程節(jié)點之間的內(nèi)部通信。經(jīng)典的 Hadoop 的高性能通信和序列化組件 Avro 的 RPC 框架抬闷,默認采用 Netty 進行跨界點通信妇蛀,它的 Netty Service 基于 Netty 框架二次封裝實現(xiàn)。

????????Netty 作為異步事件驅(qū)動的網(wǎng)絡(luò)笤成,高性能之處主要來自于其 I/O 模型和線程處理模型评架,前者決定如何收發(fā)數(shù)據(jù),后者決定如何處理數(shù)據(jù)炕泳。

2古程、體系結(jié)構(gòu)

????????Netty 功能特性如下:

? ? ? ? 1)傳輸服務(wù),支持 BIO 和 NIO喊崖。

? ? ? ? 2)容器集成挣磨,支持 OSGI、JBossMC荤懂、Spring茁裙、Guice 容器。

? ? ? ? 3)協(xié)議支持节仿,HTTP晤锥、Protobuf、二進制廊宪、文本矾瘾、WebSocket 等一系列常見協(xié)議都支持。還支持通過實行編碼解碼邏輯來實現(xiàn)自定義協(xié)議箭启。

? ? ? ? 4)Core 核心壕翩,可擴展事件模型、通用通信 API傅寡、支持零拷貝的 ByteBuf 緩沖對象放妈。

模塊組件

????????1)Bootstrap、ServerBootstrap:Bootstrap 意思是引導(dǎo)荐操,一個 Netty 應(yīng)用通常由一個 Bootstrap 開始芜抒,主要作用是配置整個 Netty 程序,串聯(lián)各個組件托启,Netty 中 Bootstrap 類是客戶端程序的啟動引導(dǎo)類宅倒,ServerBootstrap 是服務(wù)端啟動引導(dǎo)類。

????????2)Future屯耸、ChannelFuture:在 Netty 中所有的 IO 操作都是異步的拐迁,不能立刻得知消息是否被正確處理。但是可以過一會等它執(zhí)行完成或者直接注冊一個監(jiān)聽肩民,具體的實現(xiàn)就是通過 Future 和 ChannelFutures唠亚,他們可以注冊一個監(jiān)聽,當(dāng)操作執(zhí)行成功或失敗時監(jiān)聽會自動觸發(fā)注冊的監(jiān)聽事件持痰。

????????3)Channel:Netty 網(wǎng)絡(luò)通信的組件灶搜,能夠用于執(zhí)行網(wǎng)絡(luò) I/O 操作。Channel 為用戶提供:當(dāng)前網(wǎng)絡(luò)連接的通道的狀態(tài)工窍,網(wǎng)絡(luò)連接的配置參數(shù)割卖,提供異步的網(wǎng)絡(luò) I/O 操作。不同協(xié)議患雏、不同的阻塞類型的連接都有不同的 Channel 類型與之對應(yīng)鹏溯。

????????4)Selector:Netty 基于 Selector 對象實現(xiàn) I/O 多路復(fù)用,通過 Selector 一個線程可以監(jiān)聽多個連接的 Channel 事件淹仑。當(dāng)向一個 Selector 中注冊 Channel 后丙挽,Selector 內(nèi)部的機制就可以自動不斷地查詢(Select) 這些注冊的 Channel 是否有已就緒的 I/O 事件(例如可讀肺孵,可寫,網(wǎng)絡(luò)連接完成等)颜阐,這樣程序就可以很簡單地使用一個線程高效地管理多個 Channel 平窘。

????????5)NioEventLoop:NioEventLoop 中維護了一個線程和任務(wù)隊列,支持異步提交執(zhí)行任務(wù)凳怨,線程啟動時會調(diào)用 NioEventLoop 的 run 方法瑰艘,執(zhí)行 I/O 任務(wù)和非 I/O 任務(wù)。

????????6)NioEventLoopGroup:NioEventLoopGroup肤舞,主要管理 eventLoop 的生命周期紫新,可以理解為一個線程池,內(nèi)部維護了一組線程李剖,每個線程(NioEventLoop)負責(zé)處理多個 Channel 上的事件芒率,而一個 Channel 只對應(yīng)于一個線程。

????????7)ChannelHandler: 是一個接口杖爽,處理 I/O 事件或攔截 I/O 操作融击,并將其轉(zhuǎn)發(fā)到其 ChannelPipeline(業(yè)務(wù)處理鏈)中的下一個處理程序使碾。

????????8)ChannelHandlerContext:保存 Channel 相關(guān)的所有上下文信息漾岳,同時關(guān)聯(lián)一個 ChannelHandler 對象蹋砚。

????????9)ChannelPipline:保存 ChannelHandler 的 List,用于處理或攔截 Channel 的入站事件和出站操作化焕。ChannelPipeline 實現(xiàn)了一種高級形式的攔截過濾器模式萄窜,使用戶可以完全控制事件的處理方式,以及 Channel 中各個的 ChannelHandler 如何相互交互撒桨。

3查刻、IO模型

????????用什么樣的通道將數(shù)據(jù)發(fā)送給對方,BIO凤类、NIO 或者 AIO穗泵,I/O 模型在很大程度上決定了框架的性能。

????????阻塞 I/O:每個請求都需要獨立的線程完成數(shù)據(jù) Read谜疤,業(yè)務(wù)處理佃延,數(shù)據(jù) Write 的完整操作問題。當(dāng)并發(fā)數(shù)較大時夷磕,需要創(chuàng)建大量線程來處理連接履肃,系統(tǒng)資源占用較大。連接建立后坐桩,如果當(dāng)前線程暫時沒有數(shù)據(jù)可讀尺棋,則線程就阻塞在 Read 操作上,造成線程資源浪費绵跷。?I/O 是面向字節(jié)流或字符流的膘螟,以流式的方式順序地從一個 Stream 中讀取一個或多個字節(jié), 因此也就不能隨意改變讀取指針的位置成福。

????????I/O 復(fù)用模型:在 I/O 復(fù)用模型中,會用到 Select萍鲸,這個函數(shù)也會使進程阻塞闷叉,但是和阻塞 I/O 所不同的是這兩個函數(shù)可以同時阻塞多個 I/O 操作。而且可以同時對多個讀操作脊阴,多個寫操作的 I/O 函數(shù)進行檢測,直到有數(shù)據(jù)可讀或可寫時蚯瞧,才真正調(diào)用 I/O 操作函數(shù)嘿期。Netty 的非阻塞 I/O 的實現(xiàn)關(guān)鍵是基于 I/O 復(fù)用模型。由于讀寫操作都是非阻塞的埋合,這就可以充分提升 IO 線程的運行效率备徐,避免由于頻繁 I/O 阻塞導(dǎo)致的線程掛起。一個 I/O 線程可以并發(fā)處理 N 個客戶端連接和讀寫操作甚颂,這從根本上解決了傳統(tǒng)同步阻塞 I/O 一連接一線程模型蜜猾,架構(gòu)的性能、彈性伸縮能力和可靠性都得到了極大的提升振诬。在 NIO 中蹭睡,引入了 Channel 和 Buffer 的概念,只能從 Channel 中讀取數(shù)據(jù)到 Buffer 中或?qū)?shù)據(jù)從 Buffer 中寫入到 Channel赶么〖缁恚基于 Buffer 操作不像傳統(tǒng) IO 的順序操作,NIO 中可以隨意地讀取任意位置的數(shù)據(jù)辫呻。

4清钥、線程處理模型

????????數(shù)據(jù)報如何讀取放闺?讀取之后的編解碼在哪個線程進行祟昭,編解碼后的消息如何派發(fā),線程模型的不同怖侦,對性能的影響也非常大篡悟。

????????事件驅(qū)動模型:發(fā)生事件,主線程把事件放入事件隊列础钠,在另外線程不斷循環(huán)消費事件列表中的事件恰力,調(diào)用事件對應(yīng)的處理邏輯處理事件。事件驅(qū)動方式也被稱為消息通知方式旗吁,其實是設(shè)計模式中觀察者模式的思路踩萎。主要包括 4 個基本組件:1)事件隊列(event queue):接收事件的入口,存儲待處理事件很钓。2)分發(fā)器(event mediator):將不同的事件分發(fā)到不同的業(yè)務(wù)邏輯單元香府。3)事件通道(event channel):分發(fā)器與處理器之間的聯(lián)系渠道董栽。4)事件處理器(event processor):實現(xiàn)業(yè)務(wù)邏輯,處理完成后會發(fā)出事件企孩,觸發(fā)下一步操作锭碳。

? ??????Reactor 線程模型:Reactor 是反應(yīng)堆的意思,Reactor 模型是指通過一個或多個輸入同時傳遞給服務(wù)處理器的服務(wù)請求的事件驅(qū)動處理模式勿璃。服務(wù)端程序處理傳入多路請求擒抛,并將它們同步分派給請求對應(yīng)的處理線程,Reactor 模式也叫 Dispatcher 模式补疑,即 I/O 多了復(fù)用統(tǒng)一監(jiān)聽事件歧沪,收到事件后分發(fā)(Dispatch 給某進程),是編寫高性能網(wǎng)絡(luò)服務(wù)器的必備技術(shù)之一莲组。Reactor 模型中有 2 個關(guān)鍵組成:1)Reactor诊胞,Reactor 在一個單獨的線程中運行,負責(zé)監(jiān)聽和分發(fā)事件锹杈,分發(fā)給適當(dāng)?shù)奶幚沓绦騺韺?IO 事件做出反應(yīng)撵孤。它就像公司的電話接線員,它接聽來自客戶的電話并將線路轉(zhuǎn)移到適當(dāng)?shù)穆?lián)系人竭望。2)Handlers邪码,處理程序執(zhí)行 I/O 事件要完成的實際事件,類似于客戶想要與之交談的公司中的實際官員市框。Reactor 通過調(diào)度適當(dāng)?shù)奶幚沓绦騺眄憫?yīng) I/O 事件霞扬,處理程序執(zhí)行非阻塞操作。

Reactor 模型

????????Netty 主要基于主從 Reactors 多線程模型(如下圖)做了一定的修改枫振,其中主從 Reactor 多線程模型有多個 Reactor:MainReactor 負責(zé)客戶端的連接請求喻圃,并將請求轉(zhuǎn)交給 SubReactor。SubReactor 負責(zé)相應(yīng)通道的 IO 讀寫請求粪滤。非 IO 請求(具體邏輯處理)的任務(wù)則會直接寫入隊列斧拍,等待 worker threads 進行處理。

????????這里引用 Doug Lee 大神的 Reactor 介紹:Scalable IO in Java 里面關(guān)于主從 Reactor 多線程模型的圖:


主從 Rreactor 多線程模型

5杖小、與Mina的對比

????????Netty和Mina都是Trustin Lee(韓國人)的作品肆汹,Netty更晚。GitHub主頁地址 :https://github.com/trustin予权。盡管創(chuàng)作者現(xiàn)在已經(jīng)不專注與開發(fā)了昂勉。但是框架的后續(xù)開發(fā)和繼承,可以說都是符合最開始的設(shè)定的扫腺。兩個框架的架構(gòu)設(shè)計思路基本一致岗照。Netty從某種程度上講是Mina的延伸和擴展。解決了一些Mina上的設(shè)計缺陷,也優(yōu)化了一下Mina上面的設(shè)計理念攒至。

????????Mina將內(nèi)核和一些特性的聯(lián)系過于緊密厚者,使得用戶在不需要這些特性的時候無法脫離,相比下性能會有所下降迫吐,Netty解決了這個設(shè)計問題库菲;Netty的文檔更清晰;Netty更新周期更短志膀,新版本的發(fā)布比較快熙宇;架構(gòu)差別不大,Mina靠apache生存溉浙,而Netty靠jboss奇颠,和jboss的結(jié)合度非常高。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末放航,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子圆裕,更是在濱河造成了極大的恐慌广鳍,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吓妆,死亡現(xiàn)場離奇詭異赊时,居然都是意外死亡,警方通過查閱死者的電腦和手機行拢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門祖秒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人舟奠,你說我怎么就攤上這事竭缝。” “怎么了沼瘫?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵抬纸,是天一觀的道長。 經(jīng)常有香客問我耿戚,道長湿故,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任膜蛔,我火速辦了婚禮坛猪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘皂股。我一直安慰自己墅茉,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著躁锁,像睡著了一般纷铣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上战转,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天搜立,我揣著相機與錄音,去河邊找鬼槐秧。 笑死啄踊,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的刁标。 我是一名探鬼主播颠通,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼膀懈!你這毒婦竟也來了顿锰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤启搂,失蹤者是張志新(化名)和其女友劉穎硼控,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胳赌,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡牢撼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了疑苫。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熏版。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖捍掺,靈堂內(nèi)的尸體忽然破棺而出撼短,到底是詐尸還是另有隱情,我是刑警寧澤乡小,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布阔加,位于F島的核電站,受9級特大地震影響满钟,放射性物質(zhì)發(fā)生泄漏胜榔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一湃番、第九天 我趴在偏房一處隱蔽的房頂上張望夭织。 院中可真熱鬧,春花似錦吠撮、人聲如沸尊惰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弄屡。三九已至题禀,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間膀捷,已是汗流浹背迈嘹。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留全庸,地道東北人秀仲。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像壶笼,于是被迫代替她去往敵國和親神僵。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,884評論 2 354