1. 程式人生 > >多執行緒設計模式——Read-Write Lock模式和Future模式分析

多執行緒設計模式——Read-Write Lock模式和Future模式分析

# [TOC] >本文內所有實現的程式碼均附在文末,有需要可以參考。~~(好奇寶寶們可以貼上下來跑一下~~ ## 多執行緒程式評價標準 - 安全性: ​ 安全性就是不損壞物件。也就是保證物件內部的欄位的值與預期相同。 - 生存性: ​ 生存性是指無論什麼時候,必要的處理都一定能夠執行。失去生存性最典型的例子就是“死鎖”。 - 可複用性: ​ 指類能夠重複利用。若類能夠作為元件從正常執行的軟體裡分割出來,說明這個類有很高的複用性。 - 效能: ​ 指能夠快速、大批量地執行處理。主要影響因素有:吞吐量、響應性、容量等。 這裡還要再區分一下這四條。**前兩條**是程式**正常執行**的必要條件;**後兩條**是程式**提高質量**的必要條件。 ## 任何模式都有一個相同的“中心思想” “**安全性和生存性是基礎**,是所有模式都必須保證的;**可複用性和效能是目的**,是所有模式誕生的意義。” 上面這句話是我們每一個使用設計模式的人,甚至是自己編寫程式碼的人所應該牢記在心的。 在接下來分析的兩個模式中,我會用實際的設計模式的例子來幫助大家理解上面這句話的含義。 ## Read-Write Lock 模式 ### RW-Lock模式特點 - 在執行讀取操作之前,執行緒必須獲取用於讀取的鎖。 - 在執行寫入操作之前,執行緒必須獲取用於寫入的鎖。 - 多個執行緒可以同時讀取,但是讀取時,不可以寫入。 - 至多有一個執行緒正在寫入,此時其他執行緒不可以讀取或寫入。 一般來說,執行互斥處理(也是必要的)會降低程式效能(這裡的互斥處理指使用**synchronized關鍵字**)。但是通過這個模式,將針對寫入的互斥處理和讀取的互斥處理分開考慮,則可以提高程式效能。(具體效能提升效果請見下文“**效能對比**”一節) ### 衝突總結 多執行緒讀寫時總共有4種情況,會發生衝突的有三種。下面給出衝突表格: | | **讀取** | **寫入** | | :--: | :------------------------: | :------------------------: | | 讀取 | 無衝突 | 讀和寫的衝突 RW Conflict | | 寫入 | 讀和寫的衝突 RW Conflict | 寫和寫的衝突 WW Conflict | ### 手搓RW Lock模式程式碼 這部分內容是為了幫助大家更好地理解RW Lock的實現原理和過程,實際操作中我們不必編寫這麼多程式碼來實現讀寫鎖。但是在這裡強烈建議認真閱讀此部分,瞭解原理後對**使用JAVA自帶包或是實現特殊需求**都會大有裨益! #### 類圖 ![](https://img2020.cnblogs.com/blog/1976415/202004/1976415-20200402113133499-1026760828.jpg) Data類中的buffer欄位是讀寫的資訊。ReaderThread類是讀取的執行緒,WriterThread是寫入的執行緒。Data類中還保有一個ReadWriteLock類的例項,它是這個模式的主角,起到保護讀寫的作用。 #### Data類 ![](https://img2020.cnblogs.com/blog/1976415/202004/1976415-20200402114025490-1084681448.png) 第一行紅線處,lock是一個ReadWriteLock類的例項,起到保護讀寫的作用。 第二、三行紅線處,分別是readLock方法和readUnlock方法,夾在中間的是doRead方法(進行讀取的方法)。 第四、五行紅線處,分別是writeLock方法和writeUnlock方法,夾在中間的是doWrite方法(進行寫入的方法)。 Data類中還有用於模擬耗時的方法,即假定寫入操作耗時比讀取長(符合通常程式的情況)。 這裡提到的**”夾在中間“**的說法,其實是另一種設計模式——**“Before/After模式”**。由於它的使用有一些坑點,我這裡先“中斷”一下,簡單講一下“Before/After模式”。 #### P.S. Before/After模式 ```java 前置處理(此模式中為獲取鎖) try{ 實際的操作(有return也會執行finally語句塊中的內容) } finally { 後置處理(此模式中為釋放鎖) } ``` 以上程式碼為Before/After模式的基本框架。 此模式使用有兩點要特別注意!!! - try語句後面一定要跟著**finally**語句塊!finally語句塊的含義是:只要進入了try語句塊,就一定會在最後執行一次finally語句塊內的程式碼,即使try語句塊內有return語句也會執行。在這個模式中,使用finally語句就保證了,獲取的鎖在最後一定會被**釋放掉**,避免"死鎖"發生。 - 前置處理的語句一定要放在try語句塊**外面**!這一點可能會有很多人不理解,放在裡面還是外面有什麼區別?回答是:在**絕大多數情況**下,確實沒有區別。 但是當執行緒被interrupt時,程式就有可能出現過多呼叫readUnlock和writeUnlock方法的風險。假如現在程式正在lock.readLock()中進行wait,此時該執行緒被interrupt,那麼程式會丟擲InterruptedException異常,並退出readLock方法。這時readingReaders欄位並不會遞增。 從readLock方法退出的執行緒回跳到finally語句塊,執行lock.readUnlock()。在這個方法中,,之前未遞增的readingReaders欄位會執行遞減操作,該欄位的值會與我們預期不同(變得比正常要小)。這就很有可能引發難以察覺的bug。 (上面兩段中出現的方法名和欄位不知道沒關係,它們都在下面即將介紹的ReadWriteLock類中,建議大家看完下面的ReadWriteLock類的介紹再回來理解一下這部分,很重要!!**很容易出bug!!!**) ####ReadWriteLock類 ![](https://img2020.cnblogs.com/blog/1976415/202004/1976415-20200402120925231-819578648.png) ![](https://img2020.cnblogs.com/blog/1976415/202004/1976415-20200402120943484-637768101.png) 該類中儲存有四個私有欄位,前三個欄位的含義很好理解,見圖片中的程式碼註釋。 在這裡,特別強調**preferWriter**欄位!這是保證程式執行結果達到預期的重要一環,其含義和用法需要大家好好理解。這個preferWriter代表的含義是讀取和寫入兩者之間的優先順序關係。當preferWriter欄位為true時,代表寫入優先;為false時,代表讀取優先。那麼這個讀取或寫入的優先又是如何通過這一個布林值實現的呢?這裡就體現出了ReadWriteLock類的設計巧妙之處。 我們看readLock方法中的守護模式(while+wait)的守護條件(while成立的條件)。(見上圖中第二行紅線)這行程式碼的含義是如果有正在寫入的執行緒(資料正在被寫入)或是**寫入優先**並且有正在等待寫入的執行緒,那麼讀取的執行緒就要wait。這裡,preferWriter欄位發揮了它關鍵的作用。 再看readUnlock方法中對preferWriter欄位的操作(第三行紅線)。這裡的含義是,在讀取鎖釋放時,就把preferWriter欄位置為true。因為讀取鎖釋放時,一定表示已經進行完一次讀取操作了,此時應該把優先權讓給寫入操作,所以將preferWriter置為true。 同理,writeUnlock方法中對preferWriter欄位的操作(第四行紅線)也即代表進行完一次寫入操作後,要把優先權交給讀取操作,即把preferWriter欄位置為false。 這就像兩個人卻只有一個水瓶,一個人喝完一口水之後就要把水瓶交給對方,不然就會出現渴死的現象。 那麼如果把ReadWriteLock類中的preferWriter欄位去掉,程式執行起來會是什麼樣子呢?如下: ![](https://img2020.cnblogs.com/blog/1976415/202004/1976415-20200402172705225-609058998.png) 讀取執行緒比寫入執行緒多,而且讀取操作耗時短,所以讀取執行緒會一直搶佔鎖,導致寫入執行緒無法寫入。這就是程式“渴死”的樣子了。(大家有興趣可以把文末程式碼貼上下來,把preferWriter欄位去掉自己跑一下 #### 正確執行結果 正確的執行結果應該是讀取一段時間就寫入一次,這樣不斷迴圈。所以讀取的內容應該不斷變化。結果見下圖: ![](https://img2020.cnblogs.com/blog/1976415/202004/1976415-20200402173631269-889745806.png) ![](https://img2020.cnblogs.com/blog/1976415/202004/1976415-20200402173651456-882820933.png) ### 適用場合 - **讀取操作繁重時** ​ 即read操作很耗費時間。這種情況下,使用這種模式比Single Thread Execution模式(使用synchrnized關鍵字)更適合。反之,Single Thread Execution模式效能更好。 - **讀取頻率比寫入頻率高時** ​ 該模式的優點在於Reader角色之間不會發生衝突,這樣可以避免阻塞而耗費時間。但若寫入頻率很高,則Writer角色會頻繁打斷Reader角色的讀取工作,導致效能提升不會很明顯。 ### “邏輯鎖”vs“物理鎖” 大家肯定都很熟悉通過synchronized關鍵字來進行執行緒同步控制,因為synchronized關鍵字可以獲取例項的鎖。但是這裡synchronized關鍵字所獲取的鎖是JVM為每一個例項提供的一個**物理鎖**。每個例項只有一個**物理鎖**,無論如何編寫程式,也無法改變這個**物理鎖**的執行。 我們這個Read Write Lock模式中所提供的“用於寫入的鎖”和“用於讀取的鎖”都是**邏輯鎖**。這個鎖不是JVM所規定的結構,而是程式設計人員自己實現的一種邏輯結構。這就是所謂的**邏輯鎖**。我們可以通過控制ReadWriteLock類來控制**邏輯鎖**的執行。 那麼這二者的關係是什麼呢?其實,ReadWriteLock類提供的兩個**邏輯鎖**的實現,都是依靠ReadWriteLock例項持有的**物理鎖**完成的。 而此處我們也來解釋一下上節中所說的,讀取不繁重時,使用我們自己所構建的邏輯鎖就會導致比使用synchronized關鍵字(物理鎖)多很多邏輯操作,這樣多出來的邏輯操作所耗費的時間也許會大於執行緒被阻塞的時間。這樣就會導致本模式反而會比Single Thread Execution效能差。 ### 效能對比 示例程式碼中一共有6個讀取執行緒,兩個寫入執行緒。在本節效能對比中,我讓每個讀取執行緒進行20次讀取後就輸出執行時間然後終止。以下兩張圖分別為使用Read-Write Lock模式耗時和使用synchronized關鍵字耗時。 Read-Write Lock模式: ![](https://img2020.cnblogs.com/blog/1976415/202004/1976415-20200402192908452-282397155.png) synchronized關鍵字: ![](https://img2020.cnblogs.com/blog/1976415/202004/1976415-20200402192937424-961939286.png) 從以上兩圖輸出的時間可以看出,在每個執行緒讀取20次的情況下,使用Read-Write Lock模式可以比synchronized關鍵位元組省三分之二(7秒鐘左右)的時間。這在大量讀取的程式中,會給程式效能帶來極大的提升!!!(當然對於OO第二單元電梯作業來說,由於讀寫頻率差異不大而且讀取並不繁瑣,所以在電梯程式中使用Read-Write Lock模式效能提升並不明顯。不過誰又能說得准以後會不會用到呢?) ### “中心思想”分析 - **正常執行的必要條件** ​ 本模式中,通過ReadWriteLock類中的兩個獲取鎖和兩個釋放鎖的方法來模擬了synchronized關鍵字獲取例項的鎖和釋放例項的鎖這兩個過程,從而在邏輯上保證了本模式線上程安全方面與synchronized關鍵字保護的方法完全相同。因此在**安全性和生存性**兩方面,本模式很好地完成了。 - **提升效能的必要條件** ​ 本模式中,通過找到讀取和寫入交匯的四種情況中的**讀讀無衝突**的情況,並且實現讀取鎖和寫入鎖的分離,實現了多執行緒同時讀取的效果,以此來提高頻繁讀取或是“重讀取”的程式的效能。 ​ 同時,我們不難發現,關於多執行緒同步控制的程式碼都封裝在ReadWriteLock類中,其他部分直接呼叫即可,無需進行同步控制,提高了可複用性。 ## Future 模式 ### Future模式特點 我從本模式中先提取出兩個最關鍵的核心程式碼展示一下。 `Data data = host.request(10, ‘A’);` host.request方法是啟動一個新執行緒來執行請求。但是在這行程式碼中該方法的返回值,不是新執行緒執行得到的最後結果,這個data只是一張**“提貨單”、“預約券”**。 先返回“提貨單”的意義在於這個返回值可以**立即得到**,不用等待請求處理執行緒返回最後結果。在“做蛋糕”的期間,我們可以做一些**別的和“蛋糕”無關的事情**,等到“蛋糕做好了”我們再回去取“蛋糕”。 `data.getContent();` 上面這句程式碼就是執行緒“取蛋糕”的動作。這個方法的返回值是真正的“能吃的蛋糕”。 ### 手搓Future模式程式碼 #### 類圖 ![](https://img2020.cnblogs.com/blog/1976415/202004/1976415-20200402201257205-2102068285.jpg) Main類發出請求給Host類,Host類接收到請求後立刻製造一個FutureData類的例項當作**提貨券**返回給Main類,同時Host類立刻啟動一個新執行緒來處理請求(假設此處請求處理需要花費相當長時間),最後處理結果得到RealData類(蛋糕)。 #### Main類 ![](https://img2020.cnblogs.com/blog/1976415/202004/1976415-20200402202454223-2066806202.png) Main類中,向Host類發出了三個請求。之後Main執行緒就去做別的工作了,我們這裡用sleep(2000)來模擬。做完別的工作之後,Main執行緒輸出請求的結果。 #### Host類 ![](https://img2020.cnblogs.com/blog/1976415/202004/1976415-20200402203002367-194142654.png) 第一個紅線處,通過Future這個FutureData類的例項(共享物件),將Main執行緒(買蛋糕的人)和realdata(蛋糕)建立起了,超越“時空”的聯絡。 為什麼說“時空”呢?我自己的理解這個模式,就是在主執行緒得到**提貨券**後,主執行緒不管在何時何地(這裡的空間是抽象空間,也即主執行緒不在處理請求執行緒的”執行緒空間"內)都可以在結果計算出來後即時獲取結果。 第二個紅線處,使用了一個不太常用的語法模式——匿名內部類。讀者不必對這個語法熟練掌握,只需要知道在示例程式裡,這個類新建了一個處理請求的執行緒例項並讓新的執行緒執行起來去處理請求即可。(count和c變數前面都加上final關鍵字是匿名內部類的要求,瞭解即可) 說到這裡,對於每個新的請求都啟動一個新的執行緒來處理是另一個多執行緒設計模式——**Thread-Per-Message模式**。這個模式較為簡單,感興趣的讀者可以自行學習瞭解一下,這裡不再贅述了。 #### FutureData類 ![](https://img2020.cnblogs.com/blog/1976415/202004/1976415-20200402205146643-1258888985.png) 第一個紅線處,這裡設計的又是一個新的多執行緒設計模式——Balk模式。Balk模式的“中心思想”是**不要我就走了**。即當有多個執行緒時,其中一個執行緒已經完成了請求,那麼別的執行緒來要完成請求時,這個模式就通過if條件告訴執行緒:“我已經完成我的請求了,不用你再來工作了,你可以走了。”,通過return將執行緒返回回去。 第二、三個紅線處,即在請求處理執行緒完成“蛋糕”的交付之後(`this.realdata = realdata;`),將ready欄位置true,表明“蛋糕”已經隨時可以取走了。然後通知所有等待執行緒。 第四、五個紅線處,使用守護模式,以沒有ready作為守護條件,即如果“蛋糕”還沒有做好,“取蛋糕”的執行緒就要wait。否則通過getContent方法返回回去。 #### RealData類 ![](https://img2020.cnblogs.com/blog/1976415/202004/1976415-20200402205555900-1582998726.png) 第一個紅線處,這個String欄位在本示例程式中代表“蛋糕”。 第二個紅線處,示例程式中用sleep來模擬耗時很長的請求處理過程。 #### 執行結果 ![](https://img2020.cnblogs.com/blog/1976415/202004/1976415-20200402205945914-184360623.png) 通過結果輸出來看,在主執行緒執行其他工作的時候,與此同時請求正在被處理,這樣極大地提高了處理效率。 ### 模式分析 - **提高吞吐量** ​ 單核CPU中,純計算過程是無法提高吞吐量的。其他情況均可。 - **非同步方法呼叫** ​ 通過Thread-Per-Message模式通過新建執行緒,模擬實現了非同步。但是Thread-Per-Message模式無法接收返回值。 - **“準備”和“使用”返回值的分離** ​ 為了解決Thread-Per-Message模式無法接收返回值的尷尬局面,Future模式橫空出世。Future模式通過將**準備**返回值(返回**提貨券**)和**使用**返回值(呼叫getContent方法)分離,即解決了非同步呼叫無法接收返回值的問題,又提高了效能。 ### 與生產者-消費者模式有區別嗎? 答案是有。 生產者-消費者模式大家都很熟悉,通過一個tray來將生產者生產產品(有的人將其對應為本模式的請求處理過程)和消費者使用產品(有的人將其對應為本模式的使用返回值過程)分離開來。目前來看,沒有什麼區別。 但是,我們仔細想一想,Future模式通過一張**提貨券**將“生產者“和”消費者“建立起來一對一的獨一無二的聯絡。也就是說我有這個”蛋糕”的**提貨券**,我只能取我這個自己的“蛋糕”,而不能取“蛋糕店”裡做好的別人的“蛋糕”。說到這裡,相信大家都已經發現本模式與生產者-消費者模式最大的區別了吧。 ### 模式拓展 - **不讓主執行緒久等的Future角色** ​ 在示例程式中,如果FutureData的getContent方法被呼叫時,RealData類的例項還沒有建立完成,則要主執行緒**wait**建立完成,有時這也會對主執行緒的效率造成損失。 ​ 所以,為了避免這種情況的發生,我們可以將守護模式換成Balk模式,即主執行緒來“取蛋糕”時,若“蛋糕”還沒做好,就讓主執行緒返回,**再等一會兒**。這樣主執行緒可以繼續進行其他工作,過一定時間後再回來“取蛋糕”。 - **會發生變化的Future角色** ​ 通常情況下,返回值只會被設定到Future角色中一次。但是在有時需要不斷反覆設定返回值時,可以考慮給Future角色賦予“當前返回值”,即這個返回值會不斷隨時間而改變。 ​ 例如:在通過網路獲取影象資料時,可以在最開始獲取影象的長和寬,接著獲取模糊影象資料,在獲取清晰影象資料。此時,這個不斷變化的Future角色可能會大有用處。 ### 模式思考 在課上,老師提示我,是否可以用簡單的方法實現**主動返回值**的Future模式。 目前,我只想到使用**回撥**模式,在Future模式返回值設定好後,通過Host類**回撥**主執行緒。不過,使用這種方式會導致Main類裡多出很多與多執行緒同步處理相關的程式碼,導致Main類變的臃腫,而且整個模式可複用性也會降低。 我在想出好的解決辦法之後會及時更新本文,向大家展示。同時也歡迎各位讀者有好的解決辦法在評論區留言。 ### Future模式“中心思想” - **正常執行**必要條件 本模式類似生產者-消費者的邏輯,將處理與請求分離,分離的同時建立起超越“時空”的聯絡,保證了最後結果傳輸的準確性。 - **提高效能**必要條件 通過將“準備”返回值和“使用”返回值分離,將主執行緒從漫長的請求處理過程解放出來,讓主執行緒在請求處理期間,可以做別的工作,提高效能。 ## 偉大的Concurrent包! ### RW Lock模式 JAVA提供了java.util.concurrent.locks包來提供讀寫鎖的實現。這個包裡的ReentrantReadWriteLock類實現了ReadWriteLock介面。這個包的實現原理即為上述手搓RW-Lock模式程式碼所講解的原理和實現。具體使用方法很簡單,在理解原理之後使用很簡單,就不多贅述了。 ### Future模式 JAVA提供了java.util.concurrent.Future介面相當於本模式中的Future角色。其中java.util.concurrent.FutureTask類是實現了Future介面的標準類,主要有get(獲取返回值)、set(設定返回值)、cancel(中斷請求處理執行)和setException(設定異常)四個方法。 其原理和上述Future模式的手搓程式碼原理完全一致,相信大家完全理解上述講解後,對這些concurrent包的使用一定會更加得心應手!! ## 示例程式程式碼 - RW Lock模式 ```java public class Main { public static void main(String[] args) { Data data = new Data(10); Thread Reader1 = new ReaderThread(data); Reader1.start(); Thread Reader2 = new ReaderThread(data); Reader2.start(); Thread Reader3 = new ReaderThread(data); Reader3.start(); Thread Reader4 = new ReaderThread(data); Reader4.start(); Thread Reader5 = new ReaderThread(data); Reader5.start(); Thread Reader6 = new ReaderThread(data); Reader6.start(); Thread Writer1 = new WriterThread(data, "ABCDEFGHIJKLMNOPQTSTUVWXYZ"); Writer1.start(); Thread Writer2 = new WriterThread(data, "abcdefghijklmnopqrstuvwxyz"); Writer2.start(); Scanner input = new Scanner(System.in); String end = input.nextLine(); while (end.equals("")) { end = input.nextLine(); } Reader1.interrupt(); Reader2.interrupt(); Reader3.interrupt(); Reader4.interrupt(); Reader5.interrupt(); Reader6.interrupt(); Writer1.interrupt(); Writer2.interrupt(); } } public class Data { private final char[] buffer; private ReadWriteLock lock = new ReadWriteLock(); public Data(int size) { this.buffer = new char[size]; for (int i = 0; i < buffer.length; i++) { buffer[i] = '*'; } } public synchronized char[] read() throws InterruptedException { lock.readLock(); try { return doRead(); } finally { lock.readUnlock(); } } public synchronized void write(char c) throws InterruptedException { lock.writeLock(); try { doWrite(c); } finally { lock.writeUnlock(); } } private char[] doRead() { char[] newbuf = new char[buffer.length]; for (int i = 0; i < buffer.length; i++) { newbuf[i] = buffer[i]; } slowly(); return newbuf; } private void doWrite(char c) { for (int i = 0; i < buffer.length; i++) { buffer[i] = c; slowly(); } } private void slowly() { try { Thread.sleep(50); } catch (InterruptedException e) { } } } //此處為效能測試程式碼(即執行20次讀取,並統計時間) public class ReaderThread extends Thread { private final Data data; public ReaderThread(Data data) { this.data = data; } public void run() { try { long begin = System.currentTimeMillis(); for (int i = 0; i < 20; i++) { char[] readbuf = data.read(); System.out.println(Thread.currentThread().getName() + " reads " + String.valueOf(readbuf)); } long time = System.currentTimeMillis() - begin; System.out.println(Thread.currentThread().getName() + ":time = " + time); } catch (InterruptedException e) { } } } import java.util.Random; public class WriterThread extends Thread { private static final Random random = new Random(); private final Data data; private final String filler; private int index = 0; public WriterThread(Data data, String filler) { this.data = data; this.filler = filler; } public void run() { try { while (true) { char c = nextchar(); data.write(c); Thread.sleep(random.nextInt(3000)); } } catch (InterruptedException e) { } } private char nextchar() { char c = filler.charAt(index); index++; if (index >= filler.length()) { index = 0; } return c; } } public final class ReadWriteLock { private int readingReaders = 0; // (A)…實際正在讀取中的執行緒個數 private int waitingWriters = 0; // (B)…正在等待寫入的執行緒個數 private int writingWriters = 0; // (C)…實際正在寫入中的執行緒個數 private boolean preferWriter = true; // 若寫入優先,則為true public synchronized void readLock() throws InterruptedException { while (writingWriters > 0 || (preferWriter && waitingWriters > 0)) { wait(); } readingReaders++; // (A) 實際正在讀取的執行緒個數加1 } public synchronized void readUnlock() { readingReaders--; // (A) 實際正在讀取的執行緒個數減1 preferWriter = true; notifyAll(); } public synchronized void writeLock() throws InterruptedException { waitingWriters++; // (B) 正在等待寫入的執行緒個數加1 try { while (readingReaders > 0 || writingWriters > 0) { wait(); } } finally { waitingWriters--; // (B) 正在等待寫入的執行緒個數減1 } writingWriters++; // (C) 實際正在寫入的執行緒個數加1 } public synchronized void writeUnlock() { writingWriters--; // (C) 實際正在寫入的執行緒個數減1 preferWriter = false; notifyAll(); } } ``` - Future模式 ```java public class Main { public static void main(String[] args) { System.out.println("main BEGIN"); Host host = new Host(); Data data1 = host.request(10, 'A'); Data data2 = host.request(20, 'B'); Data data3 = host.request(30, 'C'); System.out.println("main otherJob BEGIN"); try { Thread.sleep(2000); } catch (InterruptedException e) { } System.out.println("main otherJob END"); System.out.println("data1 = " + data1.getContent()); System.out.println("data2 = " + data2.getContent()); System.out.println("data3 = " + data3.getContent()); System.out.println("main END"); } } public class Host { public Data request(final int count, final char c) { System.out.println(" request(" + count + ", " + c + ") BEGIN"); // (1) 建立FutureData的例項 final FutureData future = new FutureData(); // (2) 啟動一個新執行緒,用於建立RealData的例項 new Thread() { public void run() { RealData realdata = new RealData(count, c); future.setRealData(realdata); } }.start(); System.out.println(" request(" + count + ", " + c + ") END"); // (3) 返回FutureData的例項 return future; } } public interface Data { public abstract String getContent(); } public class RealData implements Data { private final String content; public RealData(int count, char c) { System.out.println(" making RealData(" + count + ", " + c + ") BEGIN"); char[] buffer = new char[count]; for (int i = 0; i < count; i++) { buffer[i] = c; try { Thread.sleep(100); } catch (InterruptedException e) { } } System.out.println(" making RealData(" + count + ", " + c + ") END"); this.content = new String(buffer); } public String getContent() { return content; } } public class FutureData implements Data { private RealData realdata = null; private boolean ready = false; public synchronized void setRealData(RealData realdata) { if (ready) { return; // balk } this.realdata = realdata; this.ready = true; notifyAll(); } public synchronized String getContent() { while (!ready) { try { wait(); } catch (InterruptedException e) { } } return realdata.getContent(); } } ``` >參考資料:《圖解JAVA多執行緒設計