Java多线程模式-GuardedSuspension模式

场景

当家庭主妇在厨房炒菜时,门铃响了,快递员送来快递,由于炒菜你不能马上去开门,而是让快递员在门口等一会儿,才去打开门。

guarded 保护的意思

Suspension 暂停延迟的意思

下面我们来模拟一下这个场景,快递员的职责是发出收快递的请求,家庭主妇的职责主要是接收请求并处理

代码实现

我们先来实现请求类,比如快递员的请求是接收快递,快递员可能来自顺丰,可能来自圆通,可能来自京东,家庭主妇可能在不同平台买了多种商品,分别使用这些快递

<code>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 + " ]";   } }/<code>

如果同时有多个快递员同时来家里送快递,此时家庭主妇可能让他们都等一下,因此我们需要一个存放请求的类,把快递员的请求都保存起来,然后依次处理

<code> import java.util.LinkedList; import java.util.Queue; ​ public class RequestCollection {    private final Queue queue = new LinkedList(); ​    public synchronized Request getRequest() {        while (queue.peek() == null) {            try {                wait();           } catch (InterruptedException e) {           }       }        return queue.remove();   }        public synchronized void putRequest(Request request) {        queue.offer(request);        notifyAll();   } } ​/<code>

说明:

Queue:队列,先进先出,LinkedList实现了Queue接口

peek方法:如果有元素就返回头元素,如果没有,返回null;该方法不会移除元素

remove方法:如果有元素就返回头元素,否则抛出异常

offer方法:添加元素到队列尾

getRequest方法:如果队列中没有请求,就wait;如果有就获取队列头元素;peek方法会判断队列中是否有请求,如果没有就进入wait,peek方法是保护条件,如果不满足则等待

等待的对象是:请求队列中是否有请求,等待请求队列是否发生变化,导致其发生变化的是将请求放入队列中,将请求从队列中取出

请求的结构:

while(守护条件的逻辑非){

​ 使用wait进行等待

}

执行后续处理

注意这里使用while循环,思考下是否可以使用if

如果使用if,也会进入到wait等待,问题在于如果在wait被打破进入后续处理之前,其他线程将请求处理完的话,问题就出现了,所以这里要使用while


putRequest方法:将请求放入队列的尾部;


快递员发出请求

<code> import java.util.Random; ​ public class Courier extends Thread{    private final Random random;    private final RequestCollection requestCollection; ​    public Courier(RequestCollection requestCollection, String name, long seed) {        super(name);        this.requestCollection = requestCollection;        this.random = new Random(seed);   }        public void run() {        for (int i = 0; i < 10000; i++) {            Request request = new Request("请求编号: " + i);            System.out.println(Thread.currentThread().getName() + " 发出请求 " + request);            requestCollection.putRequest(request);            try {                Thread.sleep(random.nextInt(1000));           } catch (InterruptedException e) {           }       }   } }/<code>

处理请求的家庭主妇类

<code> import java.util.Random; ​ public class Mother extends Thread{    private final Random random;    private final RequestCollection requestCollection; ​    public Mother(RequestCollection requestCollection, String name, long seed) {        super(name);        this.requestCollection = requestCollection;        this.random = new Random(seed);   } ​    public void run() {        for (int i = 0; i < 10000; i++) {            Request request = requestCollection.getRequest();            System.out.println(Thread.currentThread().getName() + " 处理请求 " + request);            try {                Thread.sleep(random.nextInt(1000));           } catch (InterruptedException e) {           }       }   } }/<code>

主函数类

<code> 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();   } }/<code>


模式解读

1、和临界区模式的区别

两种模式都使用synchronized,但是GuardedSuspension添加了判断条件

2、注意wait和notify的使用

如果没有notify,那么会一直等待。

可以设置wait超时

3、可以不使用wait

这样的话一直执行while循环,此时可以使用yield方法让出执行时间

while(逻辑判断非){

​ Thread.yield();

}

这叫busy wait


LinekdBlockingQueue

下面我们使用LinkedBlockingQueue来改造上面的程序

修改RequestCollection类

<code> import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; ​ public class RequestCollection {    private final BlockingQueue queue = new LinkedBlockingQueue(); ​    public Request getRequest() {        Request req = null;        try {            req = queue.take();       } catch (InterruptedException e) {       }        return req;   }    public void putRequest(Request request) {        try {            queue.put(request);       } catch (InterruptedException e) {       }   } } ​/<code>

说明:

1、上面的take和put方法做了互斥处理,因此这里不需要使用synchronized关键字


问题:

将RequestQueue中的putRequest方法中两条语句的顺序颠倒过来,即如下:

<code> public synchronized void putRequest(Request request) {   notifyAll();        queue.offer(request);         }/<code>

先运行notifyAll,程序是否正确?

答案:正确,因为调用notifyAll后,线程仍旧持有锁,其他线程虽然被唤醒,但是马上会阻塞(不会做条件判断),只有在该方法调用后,线程释放锁,其他线程才有机会执行。

但是最好将notifyAll写在该方法的最后,因为就是比较容易理解。


问题:

对于getRequest方法

<code> public Request getRequest() {        while (queue.peek() == null) {            try {              synchronized(this) {}                wait();             }           } catch (InterruptedException e) {           }       }          return queue.remove();   }/<code>

如果将锁加在while循环代码体上,会不会出问题?

答案:

会出问题,原因:如果有两个线程都同时wait,之后被唤醒,如果此时只有一个请求,那么queue.remove会出错,因为一个线程已经取出数据,另一个就没法取出数据了。


问题:

对于getRequest方法将try/catch放在while循环外部,程序会不会出问题?

答案:会出问题,因为wait方法如果被打断,那么就会执行queue.remove方法,此时不会再调用queue.peek方法做见检查,如果此时queue中没有请求,就会出错。


问题:

如果将getRequest方法中的wait改为Thread.sleep方法,程序是否有问题?

答案:会有问题,原因是:sleep方法不会是释放锁,wait会释放实例对象的锁。由于sleep不释放锁,那么线程将会一直持有锁,而其他线程就无法放入request,那么queue.peek就会一直为null,此时程序整个就没有任何活动,这种情况称为活锁。