日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

基础构建模块

發(fā)布時(shí)間:2023/12/13 编程问答 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 基础构建模块 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

5 基礎(chǔ)構(gòu)建模塊

Java平臺類庫包含了豐富的并發(fā)基礎(chǔ)構(gòu)建模塊,例如線程安全的容器類以及各種用于協(xié)調(diào)多個(gè)相互協(xié)作的線程控制流的同步工具類(Synchronizer)。本章將介紹其中一些最有用的并發(fā)構(gòu)建模塊。

5.1同步容器類

同步容器類包括Vector和Hashtable,二者是早期JDK的一部分,此外還包括在JDK 1.2

中添加的一些功能相似的類,這些同步的封裝器類是由Collections.synchronizedXxx等工廠方

法創(chuàng)建的。這些類實(shí)現(xiàn)線程安全的方式是:將它們的狀態(tài)封裝起來,并對每個(gè)公有方法都進(jìn)行同步,使得每次只有一個(gè)線程能訪問容器的狀態(tài)。

5.1.1同步容器類的問題

所以需要客戶端加鎖,而同步容器正好是支持的。例如下操作:

?

?

2 : 在調(diào)用size和相應(yīng)的get之間,Vector的長度可能會發(fā)生變化。

?

我們可以通過在客戶端加鎖來解決不可靠迭代的問題,但要犧牲一些伸縮性:

?

5.1.2 迭代器與ConcurrentModificationException

Java 5.0人的for-each循環(huán)語法中,對容器類進(jìn)行迭代的標(biāo)準(zhǔn)方式都是使用Iterator,也無法避免在迭代期間對容器加鎖。因?yàn)樗鼈儽憩F(xiàn)出的行為是"及時(shí)失敗"( fail-fast)的。它們采用的實(shí)現(xiàn)方式是,將計(jì)數(shù)器的變化與容器關(guān)聯(lián)起來。如果在迭代期間計(jì)數(shù)器被修改,那么hasNext或next將拋出ConcurrentModificationException。

?

5.1.3 隱藏迭代器

在System.out.println()中迭代set的時(shí)候,可能會拋出ConcurrentModificationException。這里得到的教訓(xùn)是,如果狀態(tài)與保護(hù)它的同步代碼之間相隔越遠(yuǎn),那么開發(fā)人員就越容易忘記在訪問狀態(tài)時(shí)使用正確的同步。如果Hiddenlterator用synchronizedSet來包裝HashSet, 并且對同步代碼進(jìn)行封裝,那么就不會發(fā)生這種錯(cuò)誤。容器的hashCode和equals等方法也會間接地執(zhí)行迭代操作

正如封裝對象的狀態(tài)有助于雄持不變性條件一樣,封裝對_象的同步機(jī)制同_樣有助于確保實(shí)施同步策略。

?

5.2并發(fā)容器

同步容器將所有對容器狀態(tài)的訪問都串行化,以實(shí)現(xiàn)它們的線程安全性。這種方法的代價(jià)是嚴(yán)重降低并發(fā)性,Java 5.0提供了多種并發(fā)容器類來改進(jìn)同步容器的性能。

通過LinkedList來實(shí)現(xiàn)Queue。Queue上的操作不會阻塞,如果隊(duì)列為空,那么獲取元素的操作將返回空值。BlockingQueu。擴(kuò)展了Queue,增加了可阻塞的插入和獲取等操作。

在Java 5.0中增加了Concurrent HashMap,用來替代同步且基于散列的Map。以及CopyOnWriteArrayList。

Java 6也引人了Concurrent SkipListMap和ConcurrentSkipListSet,分別作為同步的SortedMap和S ortedS et的并發(fā)替代品。(以前用synchronizedMap包裝TreeMap或TreeSet)

?

5.2.1 ConcurrentHashMap

采用分段鎖。ConcurrentHashMap與其他并發(fā)容器一起增強(qiáng)了同步容器類:它們提供的迭代器不會拋出ConcurrentModificationException。

盡管有這些改進(jìn),但仍然有一些需要權(quán)衡的因素。對于一些需要在整個(gè)Map上進(jìn)行計(jì)算的方法,例如size和isEmpty,這些方法的語義被略微減弱了以反映容器的并發(fā)特性。由于size返回的結(jié)果在計(jì)算時(shí)可能已經(jīng)過期了,它實(shí)際上只是一個(gè)估計(jì)值,因此允許size返回一個(gè)近似值而不是一個(gè)精確值。雖然這看上去有些令人不安,但事實(shí)上size和isEmpty這樣的方法在并發(fā)環(huán)境下的用處很小,因?yàn)樗鼈兊姆祷刂悼傇诓粩嘧兓?span style="color:#FF0000;">因此,這些操作的需求被弱化了,以換取對其他更重要操作的性能優(yōu)化,包括get, put, containsKey和remove等。

只有當(dāng)應(yīng)用程序需要加鎖Map以進(jìn)行獨(dú)占訪問.時(shí),才應(yīng)該放棄使用ConcurrentHashhiap 。

5.2.2 額外的原子Map操作

由于ConcurrentHashMap不能被加鎖來執(zhí)行獨(dú)占訪問,因此我們無法使用客戶端加鎖來創(chuàng)建新的原子操作,例如4.4.1節(jié)中對Vector增加原子操作"若沒有則添加"。但是,一些常見的復(fù)合操作,例如"若沒有則添加"、"若相等則移除(Remove-lf-Equal)"和"若相等則替換

( Replace-If-Equal)"等,都已經(jīng)實(shí)現(xiàn)為原子操作并且在ConcurrentMap的接口中聲明,如程序清單5-7所示。如果你需要在現(xiàn)有的同步Map中添加這樣的功能,那么很可能就意味著應(yīng)該考慮使用ConcurrentMap了。

?

5.2.3寫入時(shí)復(fù)制容器:CopyOnWrite

CopyOnWriteArrayList用于替代同步List,類似地,CopyOnWriteArraySet替代同步Set。

只要正確地發(fā)布一個(gè)事實(shí)不可變的對象,那么在訪問該對象時(shí)就不再需要進(jìn)一步的同步。

"寫入時(shí)復(fù)制"容器的迭代器保留一個(gè)指向底層基礎(chǔ)數(shù)組的引用(所以不會被修改因此在對其進(jìn)行同步時(shí)只需確保數(shù)組內(nèi)容的可見性),這個(gè)數(shù)組當(dāng)前位于迭代器的起始位置。當(dāng)多個(gè)線程可以同時(shí)對這個(gè)容器進(jìn)行迭代,不會彼此干擾(各自遍歷各自的引用),迭代器不會拋出ConcurrentModificationException(引用不會被修改)。也不會與修改容器的線程相互干擾。返回的并且返回的元素與迭代器創(chuàng)建時(shí)的元素完全一致。在每次修改時(shí),都會創(chuàng)建并重新發(fā)布一個(gè)新的容器副本,從而實(shí)現(xiàn)可變性(因?yàn)榘l(fā)布所以可見)。

每當(dāng)修改容器時(shí)都會復(fù)制底層數(shù)組,這需要一定的開銷,特別是當(dāng)容器的規(guī)模較大

時(shí)。僅當(dāng)?shù)僮鬟h(yuǎn)遠(yuǎn)多于修改操作時(shí),才應(yīng)該使用"寫入時(shí)復(fù)制"容器。(事件通知系統(tǒng))

5.3 阻塞隊(duì)列和生產(chǎn)者——消費(fèi)者模式

阻塞隊(duì)列提供了可阻塞的put和take方法,以及支持定時(shí)的。ffer和poll方法。BlockingQueue簡化了生產(chǎn)者一消費(fèi)者設(shè)計(jì)的實(shí)現(xiàn)過程,它支持任意數(shù)量的生產(chǎn)者和消費(fèi)者。最常見的就是線程池與隊(duì)列的組合。

在構(gòu)建高可靠的應(yīng)用程序時(shí),有界隊(duì)列是一種強(qiáng)有力的資添管理工具,一倉們能抑制并防止產(chǎn)生過多的工作項(xiàng),使程序更加健壯。

5.3.2 串行線程封閉

在java.util.coricurrent中實(shí)現(xiàn)的各種阻塞隊(duì)列都包含了足夠的內(nèi)部同步機(jī)制,從而安全地將對象從生產(chǎn)者線程發(fā)布到消費(fèi)者線程。這種安全的發(fā)布確保了對象狀態(tài)對于新的所有者來說是可見的,并且由于最初的所有者不會再訪問它,因此對象將被封閉在新的線程中。新的所有者線程可以有獨(dú)占的訪問權(quán)。

對象池利用了串行線程封閉,將對象"借給"一個(gè)請求線程。只要對象池包含足夠的內(nèi)部同步來安全地發(fā)布池中的對象,并且只要客戶代碼本身不會發(fā)布池中的對象,或者在將對象返回給對象池后就不再使用它(總之不會再讓第三個(gè)人來操作此對象),那么就可以安全地在線程之間傳遞所有權(quán)。

我們也可以使用其他發(fā)布機(jī)制來傳遞可變對象的所有權(quán),但必須確保只有一個(gè)線程能接受被轉(zhuǎn)移的對象。阻塞隊(duì)列簡化了這項(xiàng)工作。除此之外,還可以通過ConcurrentMap的原子方法remove或者AtomicReference的原子方法compareAndSet來完成這項(xiàng)工作。

?

5.3.3 雙端隊(duì)列與工作密取

工作密取非常適用于既是消費(fèi)者也是生產(chǎn)者的場景。

Java 6增加了兩種容器類型,Deque(發(fā)音為"deck")和BfockingDeque,它們分別對Queue和BlockingQueue進(jìn)行了擴(kuò)展。Deque是一個(gè)雙端隊(duì)列,實(shí)現(xiàn)了在隊(duì)列頭和隊(duì)列尾的高效插人和移除。具體實(shí)現(xiàn)包括ArrayDeque和LinkedBlockingDeque。

工作密取設(shè)計(jì)中,每個(gè)消費(fèi)者都有各自的雙端隊(duì)列。如果一個(gè)消費(fèi)者完成了自己雙端隊(duì)列中的全部工作,那么它可以從其他消費(fèi)者雙端隊(duì)列末尾秘密地獲取工作。在大多數(shù)時(shí)候,它們都只是訪問自己的雙端隊(duì)列,從而極大地減少了競爭。它會從隊(duì)列的尾部而不是從頭部獲取工作,因此進(jìn)一步降低了隊(duì)列上的競爭程度。

?

5.4阻塞方法與中斷方法

Thread提供了interrupt方法,用于中斷線程或者查詢線程是否已經(jīng)被中斷。每個(gè)線程都有一個(gè)布爾類型的屬性,表示線程的中斷狀態(tài),當(dāng)中斷線程時(shí)將設(shè)置這個(gè)狀態(tài)。

當(dāng)在代碼中調(diào)用了一個(gè)將拋出InterruptedException異常的方法時(shí),你自己的方法也就變成了一個(gè)阻塞方法,并且必須要處理對中斷的響應(yīng)。對于庫代碼來說,有兩種基本選擇:

傳遞InterruptedException 。避開這個(gè)異常通常是最明智的策略。只需把

InterruptedException傳遞給方法的調(diào)用者。傳遞InterruptedException的方法包括,根本不捕獲該異常,或者捕獲該異常,然后在執(zhí)行某種簡單的清理工作后再次拋出這個(gè)異常。

恢復(fù)中斷。有時(shí)候不能拋出InterruptedException,例如當(dāng)代碼是Runnable的一部分時(shí)。在這些情況下,必須捕獲InterruptedException,并通過調(diào)用當(dāng)前線程上的interrupt方法恢復(fù)中斷狀態(tài),這樣在調(diào)用棧中更高層的代碼將看到引發(fā)了一個(gè)中斷,如程序清單5-10所示。

在出現(xiàn)InterruptedException時(shí)不應(yīng)該做的事情是,捕獲它但不做出任何響應(yīng)。這將使調(diào)用棧上更高層的代碼無法對中斷采取處理措施,因?yàn)榫€程被中斷的證據(jù)已經(jīng)丟失。

5.5同步工具類

同步工具類可以是任何一個(gè)對象,只要它根據(jù)其自身的狀態(tài)來協(xié)調(diào)線程的控制流。阻塞隊(duì)列可以作為同步工具類,其他類型的同步工具類還包括信號量( Semaphore )、柵欄(Barrier )以及閉鎖(Latch )。

所有的同步工具類都包含一些特定的結(jié)構(gòu)化屬性:它們封裝了一些狀態(tài),這些狀態(tài)將決定執(zhí)行同步工具類的線程是繼續(xù)執(zhí)行還是等待,此外還提供了一些方法對狀態(tài)進(jìn)行操作,以及另一些方法用于高效地等待同步工具類進(jìn)入到預(yù)期狀態(tài)。

?

5.5.1閉鎖

閉鎖是一種同步工具類,可以延遲線程的進(jìn)度直到其到達(dá)終止?fàn)顟B(tài)。閉鎖的作用相當(dāng)于一扇門:在閉鎖到達(dá)結(jié)束狀態(tài)之前,這扇門一直是關(guān)閉的,并且沒有任何線程能通過,當(dāng)?shù)竭_(dá)結(jié)束狀態(tài)時(shí),這扇門會打開并允許所有的線程通過。當(dāng)閉鎖到達(dá)結(jié)束狀態(tài)后,將不會再改變狀態(tài),因此這扇門將永遠(yuǎn)保持打開狀態(tài)。閉鎖可以用來確保某些活動直到其他活動都完成后才繼續(xù)執(zhí)行,例如:

確保某個(gè)計(jì)算在其需要的所有資源都被初始化之后才繼續(xù)執(zhí)行。

確保某個(gè)服務(wù)在其依賴的所有其他服務(wù)都已經(jīng)啟動之后才啟動。

等待直到某個(gè)操作的所有參與者(在多玩家游戲中的所有玩家)都就緒再繼續(xù)執(zhí)行)

CountDownLatch是一種靈活的閉鎖實(shí)現(xiàn),它可以使一個(gè)或多個(gè)線程等待一組事件發(fā)生。閉鎖狀態(tài)包括一個(gè)計(jì)數(shù)器,該計(jì)數(shù)器被初始化為一個(gè)正數(shù),表示需要等待的事件數(shù)量。countDown方法遞減計(jì)數(shù)器,表示有一個(gè)事件已經(jīng)發(fā)生了,而await方法等待計(jì)數(shù)器達(dá)到零,這表示所有需要等待的事件都已經(jīng)發(fā)生。如果計(jì)數(shù)器的值非零,那么await會一直阻塞直到計(jì)數(shù)器為零,或者等待中的線程中斷,或者等待超時(shí)。

?

在程序清單5-11的TestHarness中給出了閉鎖的兩種常見用法。TestHarness創(chuàng)建一定數(shù)

量的線程,利用它們并發(fā)地執(zhí)行指定的任務(wù)。一它使用兩個(gè)閉鎖,分別表示"起始門(Starting

Gate )',和"結(jié)束門(Ending Gate ) "。起始門計(jì)數(shù)器的初始值為1,而結(jié)束門計(jì)數(shù)器的初始值為工作線程的數(shù)量。每個(gè)工作線程首先要做的值就是在啟動門上等待,從而確保所有線程都就緒后才開始執(zhí)行。而每個(gè)線程要做的最后一件事情是將調(diào)用結(jié)束門的countDown方法減1,這能使主線程高效地等待直到所有工作線程都執(zhí)行完成,因此可以統(tǒng)計(jì)所消耗的時(shí)間。

如果在創(chuàng)建線程后立即啟動它們,那么先啟動的線程將"領(lǐng)先"后啟動的線程,并且活躍線程數(shù)量會隨著時(shí)間的推移而增加或減少,競爭程度也在不斷發(fā)生變化。啟動門將使得主線程能夠問時(shí)釋放所有工作線程,而結(jié)束門則使主線程能夠等待最后一個(gè)線程執(zhí)行完成,而不是順序地等待每個(gè)線程執(zhí)行完成。

?

5.5.2 FutureTask

FutureTask也可以用做閉鎖。( FutureTask實(shí)現(xiàn)了Future語義,表示一種抽象的可生成結(jié)果的計(jì)算[CPJ 4.3.3])o FutureTask表示的計(jì)算是通過Callable來實(shí)現(xiàn)的,相當(dāng)于一種可生成結(jié)果的Runnable,并且可以處于以下3種狀態(tài):等待運(yùn)行(Waiting to run ),正在運(yùn)行< Running)和運(yùn)行完成(Completed) o"執(zhí)行完成"表示計(jì)算的所有可能結(jié)束方式,包括正常結(jié)束、由于取消而結(jié)束和由于異常而結(jié)束等。當(dāng)FutureTask進(jìn)人完成狀態(tài)后,它會永遠(yuǎn)停止在這個(gè)狀態(tài)上。

Future.get的行為取決于任務(wù)的狀態(tài)。如果任務(wù)已經(jīng)完成,那么get會立即返回結(jié)果,否則get將阻塞直到任務(wù)進(jìn)人完成狀態(tài),然后返回結(jié)果或者拋出異常。FutureTask將計(jì)算結(jié)果從執(zhí)行計(jì)算的線程傳遞到獲取這個(gè)結(jié)果的線程,而FutureTask的規(guī)范確保了這種傳遞過程能實(shí)現(xiàn)結(jié)果的安全發(fā)布。

FutureTask在Executo:框架中表示異步任務(wù),此外還可以用來表示一些時(shí)間較長的計(jì)算,這些計(jì)算可以在使用計(jì)算結(jié)果之前啟動。程序清單5-12中的Preloader就使用了FutureTask來執(zhí)行一個(gè)高開銷的計(jì)算,并且計(jì)算結(jié)果將在稍后使用。通過提前啟動計(jì)算,可以減少在等待結(jié)果時(shí)需要的時(shí)間。

由于在構(gòu)造函數(shù)或靜態(tài)初始化方法中啟動線程并不是一種好方法,因此提供了一

個(gè)start方法來啟動線程。當(dāng)程序隨后需要ProductInfo時(shí),可以調(diào)用get方法,如果數(shù)據(jù)已經(jīng)加載,那么將返回這些數(shù)據(jù),否則將等待加載完成后再返回。

?

?

Callable表示的任務(wù)可以拋出受檢查的或未受檢查的異常,并且任何代碼都可能拋出一個(gè)Error。無論任務(wù)代碼拋出什么異常,都會被封裝到一個(gè)ExecutionException中,并在

Future.get中被重新拋出。這將使調(diào)用get的代碼變得復(fù)雜,因?yàn)樗粌H需要處理可能出現(xiàn)的ExecutionException(以及未檢查的CancellationException ),而且還由于ExecutionException是作為一個(gè)Throwable類返回的,因此處理起來并不容易。在Preloader中,當(dāng)get方法拋出ExecutionException時(shí),可能是以下三種情況之一:Callable拋出的受檢查異常,RuntimeException,以及Error。我們必須對每種情況進(jìn)行單獨(dú)處理,但我們將使用程序清單5-13中的launderThrowable輔助方法來封裝一些復(fù)雜的異常處理邏輯。在調(diào)用launderThrowable之前,Preloader會首先檢查已知的受檢查異常,并重新拋出它們。剩下的是未檢查異常,Preloader將調(diào)用launderThrowabl。并拋出結(jié)果。如果Throwable傳遞給launderThrowable的是一個(gè)Error,那么launderThrowable將直接再次拋出它;如果不是RuntimeException,那么將拋出一個(gè)I11ega1StateException表示這是一個(gè)邏輯錯(cuò)誤。乘下的

RuntimeException, launderThrowable將把它們返回給調(diào)用者,而調(diào)用者通常會重新拋出

它們。

?

5.5.3 信號量

計(jì)數(shù)信號量(Counting Semaphore)用來控制同時(shí)訪問某個(gè)特定資源的操作數(shù)量,數(shù)信號量還可以用來實(shí)現(xiàn)某種資源池,或者對容器施加邊界。Semaphore中管理著一組虛擬的許可(permit),許可的初始數(shù)量可通過構(gòu)造函數(shù)來指定。在執(zhí)行操作時(shí)可以首先獲得許可(只要還有剩余的許可),并在使用以后釋放許可。如果沒有許可,那么acquire將阻塞直到有許可(或者直到被中斷或者操作超時(shí))。release方法將返回一個(gè)許可給信號量。

計(jì)算信號量的一種簡化形式是二值信號量,即初始值為1的Semaphore。二值信號量可以用做互斥體(mutex),并具備不可重人的加鎖語義:誰擁有這個(gè)唯一的許可,誰就擁有了互斥鎖。

你可以使用Semaphore將任何一種容器變成有界阻塞容器。我們可以構(gòu)造一個(gè)固定長度的資源池,當(dāng)池為空時(shí),請求資源將會失敗,但你真正希望看到的行為是阻塞而不是失敗,并且當(dāng)池非空時(shí)解除阻塞。如果將Semaphore的計(jì)數(shù)值初始化為池的大小,并在從池中獲取一個(gè)資源之前首先調(diào)用acquire方法獲取一個(gè)許可,在將資源返回給池之后調(diào)用release釋放許可,那么acquire將一直阻塞直到資源池不為空。

?

5.5.4柵欄

柵欄(B arrier)類似于閉鎖,它能阻塞一組線程直到某個(gè)事件發(fā)生【CPJ 4,4.3]。柵欄與閉鎖的關(guān)鍵區(qū)別在于,所有線程必須同時(shí)到達(dá)柵欄位置,才能繼續(xù)執(zhí)行。閉鎖用于等待事件,而柵欄用于等待其他線程。柵欄用于實(shí)現(xiàn)一些協(xié)議,例如幾個(gè)家庭決定在某個(gè)地方集合:"所有人6:00在麥當(dāng)勞碰頭,到了以后要等其他人,之后再討論下一步要做的事情。

CyclicBarrier可以使一定數(shù)量的參與方反復(fù)地在柵欄位置匯集,它在并行迭代算法中非常有用:這種算法通常將一個(gè)問題拆分成一系列相互獨(dú)立的子問題。當(dāng)線程到達(dá)柵欄位置時(shí)將調(diào)用await方法,這個(gè)方法將阻塞直到所有線程都到達(dá)柵欄位置。如果所有線程都到達(dá)了柵欄位置,那么柵欄將打開,此時(shí)所有線程都被釋放,而柵欄將被重置以便下次使用。如果對await的調(diào)用超時(shí),或者await阻塞的線程被中斷,那么柵欄就被認(rèn)為是打破了,所有阻塞的await調(diào)。用都將終止并拋出BrokenBarrierException。如果成功地通過柵欄,那么await將為每個(gè)線程返回一個(gè)唯一的到達(dá)索引號,我們可以利用這些索引來"選舉"產(chǎn)生一個(gè)領(lǐng)導(dǎo)線程,并在下一次迭代中由該領(lǐng)導(dǎo)線程執(zhí)行一些特殊的工作。CyclicBarrier還可以使你將一個(gè)柵欄操作傳遞給構(gòu)造函數(shù),這是一個(gè)Runnable,當(dāng)成功通過柵欄時(shí)會(在一個(gè)子任務(wù)線程中)執(zhí)行它,但在阻塞線程被釋放之前是不能執(zhí)行的。

?

在程序清單5-15的CellularAutomata中給出了如何通過柵欄來計(jì)算細(xì)胞的自動化模擬,例如Conway的生命游戲。在把模擬過程并行化時(shí),為每個(gè)元素(在這個(gè)示例中相當(dāng)于一個(gè)細(xì)胞)分配一個(gè)獨(dú)立的線程是不現(xiàn)實(shí)的,因?yàn)檫@將產(chǎn)生過多的線程,而在協(xié)調(diào)這些線程上導(dǎo)致的開銷將降低計(jì)算性能。合理的做法是,將問題分解成一定數(shù)量的子問題,為每個(gè)子問題分配一個(gè)線程來進(jìn)行求解,之后再將所有的結(jié)果合并起來。CellularAutomata將問題分解為Ncpu個(gè)子問題,其中\(zhòng)P}:等于可用CPU的數(shù)量,并將每個(gè)子問題分配給一個(gè)線程。在每個(gè)步驟中,工作線程都為各自子問題中的所有細(xì)胞計(jì)算新值。當(dāng)所有工作線程都到達(dá)柵欄時(shí),柵欄會把這些新值提交給數(shù)據(jù)模型。在柵欄的操作執(zhí)行完以后,工作線程將開始下一步的計(jì)算,包括調(diào)用isDone方法來判斷是否需要進(jìn)行下一次迭代。

?

另一種形式的柵欄是Exchanger,它是一種兩方(Two-Party )柵欄,各方在柵欄位置上交

換數(shù)據(jù)[CPJ 3.4.3]。當(dāng)兩方執(zhí)行不對稱的操作時(shí),Exchanger會非常有用,例如當(dāng)一個(gè)線程向緩沖區(qū)寫人數(shù)據(jù),而另一個(gè)線程從緩沖區(qū)中讀取數(shù)據(jù)。這些線程可以使用Exchangez來匯合,并將滿的緩沖區(qū)與空的緩沖區(qū)交換。當(dāng)兩個(gè)線程通過Exchanger交換對象時(shí),這種交換就把這兩個(gè)對象安全地發(fā)布給另一方。

數(shù)據(jù)交換的時(shí)機(jī)取決于應(yīng)用程序的響應(yīng)需求。最簡草的方案是,當(dāng)緩沖區(qū)被填滿時(shí),

由填充任務(wù)進(jìn)行交換,當(dāng)緩沖區(qū)為空時(shí),由清空任務(wù)進(jìn)行交換。這樣會把需要交換的次數(shù)

降至最低,但如果新數(shù)據(jù)的到達(dá)率不可預(yù)測,那么一些數(shù)據(jù)的處理過程就將延遲。另一個(gè)

方法是,不僅當(dāng)緩沖被填滿時(shí)進(jìn)行交換,并且當(dāng)緩沖被填充到一定程度并保持一定時(shí)間后,

也進(jìn)行交換。

?

5.6 構(gòu)建高效且可伸縮的結(jié)果緩存

我們將開發(fā)一個(gè)高效且可伸縮的緩存。我們首先從簡單的HashMap開始,然后分析它的并發(fā)性缺陷,并討論如何修復(fù)它們。

由于ConcurrentHashMap是線程安全的,因此在訪問底層Map時(shí)就不需要進(jìn)行同步。但它仍然存在一些不足。當(dāng)兩個(gè)線程同時(shí)調(diào)用compute時(shí)存在一個(gè)漏洞,如果某個(gè)線程啟動了一個(gè)開銷很大的計(jì)算,而其他線程并不知道這個(gè)計(jì)算正在進(jìn)行,那么很可能會重復(fù)這個(gè)計(jì)算。因此在計(jì)算之間最好先判斷是否有其他線程正在進(jìn)行此計(jì)算。FutureTask類能實(shí)現(xiàn)這個(gè)功能。FutureTasko FutureTask表示一個(gè)計(jì)算的過程,這個(gè)過程可能已經(jīng)計(jì)算完成,也可能正在進(jìn)行。如果有結(jié)果可用,那么FutureTask.get將立即返回結(jié)果,否則它會一直阻塞,直到結(jié)果計(jì)算出來再將其返回。

用ConcurrentHashMap<A,Future<V>>,替換原來的ConcurrentHashMap<A, V>。首先檢查某個(gè)相應(yīng)的計(jì)算是否已經(jīng)開始,如果還沒有啟動,那么就創(chuàng)建一個(gè)FutureTask,并注冊到Map中,然后啟動計(jì)算;如果已經(jīng)啟動,那么等待現(xiàn)有計(jì)算的結(jié)果。結(jié)果可能很快會得到,也可能還在運(yùn)算過程中,但這對于Future.get的調(diào)用者來說是透明的。

ConcurrentMap中的原子方法putIfAbsent,避免了compute方法中的if代碼塊仍然是非原子(nonatomic)的"先檢查再執(zhí)行"操作。 (2個(gè)線程仍有可能在同一時(shí)間內(nèi)調(diào)用compute來計(jì)算相同的值,使用put方法則不具備原子性,不安全)。

轉(zhuǎn)載于:https://www.cnblogs.com/domi22/p/8538408.html

總結(jié)

以上是生活随笔為你收集整理的基础构建模块的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。