java多線程之Guarded Suspension模式

一、Guarded Suspension模式

Guarded是被守護,被保衛,被保護的意思,Suspension則是“暫停”的意思,如果執行現在的處理會造成問題,就讓執行處理的線程進行等待,這就是Guarded Suspension模式。

二、實例程序

在這個程序中,一個線程(ClientThread)會將請求(Request)的實例傳遞給另一個(ServerThread)。這是一種最簡單的線程間的通信。

類名 說明
Request.java 表示一個請求的類
RequestQueue.java 依次存放請求的類
ClientThread.java 發送請求的類
ServerThread.java 接受請求的類
Main.java 測試程序行爲的類

示例程序的時序圖:

img

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模式的類圖:

img

Guarded Suspension模式的Timethreads圖:

img

四、拓展思路

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 ] 

6.BlockingQueue接口與LinkedBlockingQueue類的類圖

img
代碼案例

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章