一、Guarded Suspension模式
Guarded是被守護,被保衛,被保護的意思,Suspension則是“暫停”的意思,如果執行現在的處理會造成問題,就讓執行處理的線程進行等待,這就是Guarded Suspension模式。
二、實例程序
在這個程序中,一個線程(ClientThread)會將請求(Request)的實例傳遞給另一個(ServerThread)。這是一種最簡單的線程間的通信。
類名 | 說明 |
---|---|
Request.java | 表示一個請求的類 |
RequestQueue.java | 依次存放請求的類 |
ClientThread.java | 發送請求的類 |
ServerThread.java | 接受請求的類 |
Main.java | 測試程序行爲的類 |
示例程序的時序圖:
1.Request類
用於表示請求,雖說是請求,但由於只是用於表示ClientThread傳遞給ServerThread的實例,所以不提供什麼特殊的處理。Request類只有一個名稱屬性name字段。
package com.viagra.Guarded_Suspension_Pattern.Lesson1;
/**
* @Auther: viagra
* @Date: 2019/11/19 14:58
* @Description:
*/
public class Request {
private final String name;
public Request(String name) {
this.name = name;
}
public String getName() {
return name;
}
public String toString() {
return " [ Request " + name + " ] ";
}
}
2.RequestQueue類
getRequest方法會取出最先放在RequestQueue中的一個請求,作爲其返回值,如果一個請求都沒有,那就一直等待,直到其他某個程序執行putRequest。
putRequest方法用於添加一個請求,當線程想要向RequestQueue中添加Request實例時,可以調用該方法。
總體來說,RequestQueue通過putRequest方法Request實例,並按放入順序使用getRequest取出Request實例。這種結構通常成爲隊列(queue)或FIFO(First In First Out,先進先出),例如,在銀行窗口前依次等待的隊伍就是隊列的一種。
package com.viagra.Guarded_Suspension_Pattern.Lesson1;
import java.util.LinkedList;
import java.util.Queue;
/**
* @Auther: viagra
* @Date: 2019/11/19 14:58
* @Description:
*/
public class RequestQueue {
private final Queue<Request> queue = new LinkedList<Request>();
/**
* 首先,我們來思考一下getRequest方法應該執行的目標處理是什麼?目的是“從quene中取出一個Request實例”,也就是執行下面這條語句
* queue.remove()
* 但是,爲了安全地執行這條語句,必須滿足如下條件:
* quene.peek()!=null //守護條件
*
* 仔細查看那getRequest中的while語句的條件表達式,你會發現while語句的條件表達式是守護條件的邏輯非運算,這條while語句會保證在remove方法被調用時,守護條件一定是成立的
* 當守護條件的邏輯非運算滿足時,也就是說守護條件不成立時,絕對不會繼續執行while之後的語句。
*
* 不等待的情況和等待的情況
* 當線程執行到while語句時,需要考慮守護條件成立與補成立這二種情況
* 1.當守護條件成立時(quene.peek()!=null),線程不會進入while內,而是執行while的下一條語句,調用remove()方法,這時不會執行到wait,所以線程也不會等待。
* 2.當守護條件不成立時(quene.peek()==null),線程會進入while語句內,執行wait,開始等待。
*
* 執行wait
* 開始等待,那到底等待什麼?
* “當然是等到notify/notifyAll”,當守護條件成立時(quene.peek()!=null),正在wait的線程希望被notify/notifyAll,因爲只有在這時,while後面的語句才能夠執行。
*
* @return
*/
public synchronized Request getRequest() {
while (queue.peek() == null) {
try {
wait();
} catch (Exception e) {
e.printStackTrace();
}
}
return queue.remove();
}
/**
* 處理執行offer方法 ,向queue的末尾添加一個請求(request),這時,queue中至少存在一個可供取出的元素,因此,下面的表達式爲真。
* queue.peek()!=null
* 在上面的getRequest中,正在wait的線程等待的是什麼呢?正是這個條件,即守護條件的成立,那麼,這裏就來執行nofityAll吧。
* @param request
*/
public synchronized void putRequest(Request request) {
queue.offer(request);
notifyAll();
}
/**
* synchronized保護的是queue字段(LinkedList實例),getRequest方法中的二個處理就必須確保同時“只能由一個線程執行”。
* 1.判斷queue字段中是否存在可供取出的元素
* 2.從queue字段中取出一個元素
*/
/**
* wait與鎖
* 假設線程要執行某個實例的wait方法,這時,線程必須獲取該實例的鎖,上面的synchronized方法中,wait方法被調用時,獲取的就是this的鎖
* 線程執行this的wait方法後,進入this的等待隊列,並釋放持有的this鎖。
* notify、notifyAll或interrupt會讓線程退出等待隊列,但是實際地繼續執行處理之前,還必須再獲取this的鎖。
*/
}
3.ClientThread類
ClientThread類用戶表示發送請求的線程。ClientThread持有RequestQueue的實例(requestQueue),並連續調用該實例的putRequest,放入請求,請求的名稱依次爲:No.1、No.2、…,以隨機數來作爲sleep的時間。
package com.viagra.Guarded_Suspension_Pattern.Lesson1;
import java.util.Random;
/**
* @Auther: viagra
* @Date: 2019/11/19 14:58
* @Description:
*/
public class ClientThread extends Thread {
private final Random random;
private final RequestQueue requestQueue;
public ClientThread(RequestQueue requestQueue, String name, long seed) {
super(name);
this.requestQueue = requestQueue;
this.random = new Random(seed);
}
public void run() {
for (int i = 0; i < 10000; i++) {
Request request = new Request("No. " + i);
System.out.println(Thread.currentThread().getName() + " requests: " + request);
requestQueue.putRequest(request);
try {
Thread.sleep(random.nextInt(1000));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
4.ServerThread類
ServerThread用戶表示接收請求的線程,該類也持有RequestQueue的實例(requestQueue),ServerThread使用getRequest方法接受請求。也使用隨機數來sleep。
package com.viagra.Guarded_Suspension_Pattern.Lesson1;
import java.util.Random;
/**
* @Auther: viagra
* @Date: 2019/11/19 14:58
* @Description:
*/
public class ServerThread extends Thread {
private final Random random;
private final RequestQueue requestQueue;
public ServerThread(RequestQueue requestQueue, String name, long seed) {
super(name);
this.requestQueue = requestQueue;
this.random = new Random(seed);
}
public void run() {
for (int i = 0; i < 10000; i++) {
Request request = requestQueue.getRequest();
System.out.println(Thread.currentThread().getName() + " handle: " + request);
try {
Thread.sleep(random.nextInt(1000));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
5.Main類
public class Main {
public static void main(String[] args) {
RequestQueue requestQueue = new RequestQueue();
new ClientThread(requestQueue, "Alice", 3141592L).start();
new ServerThread(requestQueue, "Bobby", 6535897L).start();
}
}
首先會創建RequestQueue的實例,然後分別創建二個實例,並將requestQueue傳給這二個實例,最後執行start。
6.運行結果
Alice requests: [ Request No. 0 ]
Bobby handle: [ Request No. 0 ]
Alice requests: [ Request No. 1 ]
Alice requests: [ Request No. 2 ]
Bobby handle: [ Request No. 1 ]
Bobby handle: [ Request No. 2 ]
Alice requests: [ Request No. 3 ]
Bobby handle: [ Request No. 3 ]
Alice requests: [ Request No. 4 ]
Bobby handle: [ Request No. 4 ]
Alice requests: [ Request No. 5 ]
Alice requests: [ Request No. 6 ]
Bobby handle: [ Request No. 5 ]
Bobby handle: [ Request No. 6 ]
Alice requests: [ Request No. 7 ]
Bobby handle: [ Request No. 7 ]
Alice requests: [ Request No. 8 ]
Bobby handle: [ Request No. 8 ]
7.java.util.Quene與java.util.LinkedList的操作
queue字段中實際保存的是實現Quene接口的java.util.LinkedList類的實例。LinkedList類標識串狀連接在一起的對象集合,可以作爲通用鏈表接口使用,程序只試用了LinkedList類的如下三個方法:
-
Request remove():該方法是移除隊列的第一個元素,並返回該元素。如果隊中一個元素都沒有,則拋出該元素不存在的異常;
-
boolean offer(request req):該方法用戶將元素req添加到隊列末尾;
-
Request peek():如果隊列中存在元素,則方法會返回頭元素。
包含的方法:
方法名 | 用法 | 描述 |
---|---|---|
add | 增加一個元索 | 如果隊列已滿,則拋出一個IIIegaISlabEepeplian異常 |
remove | 移除並返回隊列頭部的元素 | 如果隊列爲空,則拋出一個NoSuchElementException異常 |
element | 返回隊列頭部的元素 | 如果隊列爲空,則拋出一個NoSuchElementException異常 |
offer | 添加一個元素並返回true | 如果隊列已滿,則返回false |
poll | 移除並返問隊列頭部的元素 | 如果隊列爲空,則返回null |
peek | 返回隊列頭部的元素 | 如果隊列爲空,則返回null |
put | 添加一個元素 | 如果隊列滿,則阻塞 |
take | 移除並返回隊列頭部的元素 | 如果隊列爲空,則阻塞 |
代碼解讀可以參考類中的註釋。
三、Guarded Suspension模式中的登場角色
- GuardedObject(被守護的對象)
GuardedObject角色是一個持有被守護的方法(guardedMethod)的類,當線程執行guardedMethod方法時,若守護條件成立,則可以立即執行;當守護條件不成立時,就要進行等待,守護條件的成立與否會隨着GuardedObject角色的狀態不同而發生變化。
除了guardedMethod外,GuardedObject角色還有可能持有其他改變實例狀態的方法(stateChangingMethod)。
在Java中guardedMethod通過while語句和wait方法來實現,stateChangingMethod則通過notify/notifyAll方法來實現。
Guarded Suspension模式的類圖:
Guarded Suspension模式的Timethreads圖:
四、拓展思路
1.synchronized
在Guarded Suspension設計模式中,線程是否等待取決於守護條件,Guarded Suspension設計模式是在Single Threaded Execution模式的基礎上附加了條件而形成的。
2.多線程版本的if
Guarded Suspension設計模式就像是“多線程版本的if”。
3.忘記改變狀態與生存性
正在wait的線程每次被notify/notifyAll時都會檢查守護條件,不管被notify/notifyAll多少次,如果守護條件不成立,線程都會隨着while再次wait。
如果程序錯誤,那麼守護條件永遠都不會成立,這時不管執行多少次notify/notifyAll,線程處理都無法繼續,程序也就失去了生存性。
4.wait與notify/notifyAll的責任【可複用性】
wait/notifyAll只出現在RequestQueue類中,而並未出現在ClientThread、ServerThread、Main類中。Guarded Suspension模式的實現封裝在RequestQueue類中。
這種做法對RequestQueue類的可複用性來說是非常重要的。
5.各種稱呼
它們的共同特徵有如下三點:
- 存在循環
- 存在條件檢查
- 因爲某種原因而等待
1.guarded suspension:“被守護而暫停執行”的含義,該名稱並不體現其實現方法。
2.guarded wait:“被守護而等待”的意思。
等待端的示例:
while(!ready){ wait();}
喚醒端的實例:
ready = true;notifyAll();
3.busy wait:“忙於等待”的意思,其實現方法爲線程並未使用wait進行等待,而是執行yield(儘可能地將優先級讓給其他線程)的同時檢查守護條件。由於等待端的線程也是在持續運行的,所以會浪費java虛擬機的時間,yield是Thread類的靜態方法。
while (!ready){ Thread.yield();}
喚醒端的實例:
ready = true;
4.spin lock
5.polling
五、使用java.util.concurrent.LinkedBlockingQueue的示例程序
在上面的示例使用java.util.LinkedList類和Guarded Suspension模式構成了RequestQueue類,實際上,J2SE5.0的java.util.concurrent包中提供了與該RequestQueue類功能相同的一個類,就是java.util.concurrent.LinkedBlockingQueue類。
1.RequestQueue類
package com.viagra.Guarded_Suspension_Pattern.Lesson2;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* @Auther: viagra
* @Date: 2019/11/19 15:29
* @Description:
*/
public class RequestQueue {
private final BlockingQueue<Request> queue = new LinkedBlockingQueue<Request>();
public Request getRequest() {
Request req = null;
try {
req = queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return req;
}
public void putRequest(Request request) {
try {
queue.put(request);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.ClientThread類
package com.viagra.Guarded_Suspension_Pattern.Lesson2;
import com.viagra.Guarded_Suspension_Pattern.Lesson1.Request;
import com.viagra.Guarded_Suspension_Pattern.Lesson1.RequestQueue;
import java.util.Random;
/**
* @Auther: viagra
* @Date: 2019/11/19 14:58
* @Description:
*/
public class ClientThread extends Thread {
private final Random random;
private final RequestQueue requestQueue;
public ClientThread(RequestQueue requestQueue, String name, long seed) {
super(name);
this.requestQueue = requestQueue;
this.random = new Random(seed);
}
public void run() {
for (int i = 0; i < 10000; i++) {
Request request = new Request("No. " + i);
System.out.println(Thread.currentThread().getName() + " requests: " + request);
requestQueue.putRequest(request);
try {
Thread.sleep(random.nextInt(1000));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
3.ServerThread類
public class ServerThread extends Thread {
private final Random random;
private final RequestQueue requestQueue;
public ServerThread(RequestQueue requestQueue, String name, long seed) {
super(name);
this.requestQueue = requestQueue;
this.random = new Random(seed);
}
public void run() {
for (int i = 0; i < 10000; i++) {
Request request = requestQueue.getRequest();
System.out.println(Thread.currentThread().getName() + " handle: " + request);
try {
Thread.sleep(random.nextInt(1000));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
4.Main類
public class Main {
public static void main(String[] args) {
RequestQueue requestQueue = new RequestQueue();
new ClientThread(requestQueue, "Alice", 3141592L).start();
new ServerThread(requestQueue, "Bobby", 6535897L).start();
}
}
5.執行結果
Alice requests: [ Request No. 0 ]
Bobby handle: [ Request No. 0 ]
Alice requests: [ Request No. 1 ]
Alice requests: [ Request No. 2 ]
Bobby handle: [ Request No. 1 ]
Bobby handle: [ Request No. 2 ]
Alice requests: [ Request No. 3 ]
Bobby handle: [ Request No. 3 ]
Alice requests: [ Request No. 4 ]
Bobby handle: [ Request No. 4 ]
Alice requests: [ Request No. 5 ]
Alice requests: [ Request No. 6 ]
Bobby handle: [ Request No. 5 ]