JAVA多线程设计模式之Guarded Suspension

JAVA多线程设计模式之Guarded Suspension

一、什么是Guarded Suspension模式

Java中是使用while语句来检查条件,使用wait方法来执行等待的。当条件发生变化时,使用notify/notifyAll方法发出通知,这就是Guarded Suspension模式。

二、示例程序

在这个程序中,一个线程(ClientThread)会将请求(Request)的实例传递给另一个线程(ServerThread)。

类的一览表

名字 说明
Request 表示一个请求的类
RequestQueue 依次存放请求的类
ClientThread 发送请求的类
ServerThread 接受请求的类
Main 测试程序行为的类

Request类

Request类用于表示请求,用于表示CilentThread传递给ServerThread的实例。Request类只有一个名称属性(name字段)。

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

}

RequestQueue类

RequestQueue类用于依次存放请求。定义了putRquest和getRequest,先进先出,这种结构通常称为队列(queue)

import java.util.LinkedList;
import java.util.Queue;
public class requestQueue {
   private final Queue<Request> queue=new LinkedList<Request>();//队列是一种特殊的线性表,它只允许在表的前端进行删除操作,而在表的后端进行插入操作
   public synchronized Request getRequest(){                     //LinkedList类实现了Queqe接口,因此我们可以把LinkedList当成Queue来使用
       while (queue.peek()==null){  //element()和peek()用在队列的头部查询元素,与remove()方法类似,在队列为空时,element()抛出一个异常,而peek()返回null
           try {
               wait();
           }catch (InterruptedException e){
       }
   }
       return queue.remove();//删除队列的第一个元素,Queue 中 remove() 和 poll()都是用来从队列头部删除一个元素。
       //在队列元素为空的情况下,remove() 方法会抛出NoSuchElementException异常,poll() 方法只会返回 null 。

}
   public synchronized void putRequest(Request request){
       queue.offer(request);//向队列添加一个元素,也就是添加request,异常会抛出false
       notifyAll();//唤醒在此对象监视器上等待的所有线程。
   }
}
  1. Request peek()
    如果队列中存在元素,则该方法会返回头元素;如果为空,则返回null。在这里getRequest的的目的是“从queue中取出一个Request实例”,也就是为了执行queue.remove。但是提前条件必须是“存在想要取出的元素”,这种必须要满足的条件就称为Guard Suspension模式的守护条件。我认为该模式的核心也就是要做好守护条件的编写和维护。

  2. 执行wait,等待条件发生变化
    当守护条件不成立时,进行等待,他是在等待着什么呢?是在等待着“notify/notifyAll”。是这样的吗,线程真正在等待的是实例状态的变化,线程之所以在等待,是因为守护条件没满足。只有知道线程在等待着什么,才会明白应该何时执行notify/notifyAll。
    在Guarded Suspension模式中,使用while来检查守护条件是非常重要的。notify/notifyAll只不过是检查守护条件的触发器而已。

  3. 将wait替换成Thread.sleep可以吗
    不可以,会发生问题。wait与Thread.sleep不同,执行wait的线程会释放对象实例的锁,而Thread.sleep不会释放实例的锁。因此如果getRequest方法一直持有锁,其他线程无法进入putRequest,守护状态一直不会改变,所以就陷入了不断地死循环,失去了生存性。

  4. 将whlie换成if可以吗
    不可以。这种情况在本例不会发生问题,但是一般情况还是会有问题。
    while语句属于循环语句,在判断是,如果条件为true,则会继续判断,直到false为止,即会进行多次判断。
    if语句属于条件判断语句,如果条件是true,则继续执行,为false则跳出语句不执行,只会进行单次判断。
    延伸到本例来看,使用if, 假设queue中只有一个元素,线程全部notifyAll之后,第一个开始运行的线程调用queue.remove()之后,queue会变成空,若为空接下来的线程因为守护条件就无法进行,可是如果之后有几个线程中,已经判断守护条件成立,那也会调用remove,这样就会出现问题,if不会再次判断了,while会再次判断就不会出现问题。就像下面举得这个例子一样。
    试想一下这个场景,假如你在开车,遇到红灯后就停下来,坐在那里发呆,当你的副驾驶突然提醒你可以走了,你也不能马上踩油门来走,而是确认一下等已经变绿之后才可以开车,if和while地区别就在这里。

  5. Request remove()
    该方法用于移除队列的第一个元素,并返回该元素。如果队列中一个元素都没有,则会抛出异常。pull()也是有相同的作用,只是当队列元素为空时,只会返回null。

  6. queue.offer(request)与notifyAll()能否颠倒顺序?
    可以,在执行notifyAll()之后,即使其他线程被唤醒了,但是由于此线程还拥有着锁,其他线程会阻塞,没有什么进展,只有全部执行完以后,才会返回。不过,将notifyAll()写在后面更容易理解,实例变化了,才因此执行唤醒操作

ClientThread类

RequestQueue类用于表示发送请求的线程。ClientThread持有RequestQuene的实例(requestQuene),并连续调用该实例的putRequest,放入请求。为了错开发送请求(执行putRequest)的时间点,用了sleep。

import java.util.Random;
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);
     }

   @Override
   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 (InterruptedException e) {
           }
       }
   }
}

ServerThread类

RequestQueue类用于表示接受请求的线程。ServerThread持有RequestQuene的实例(requestQuene),并连续调用该实例的getRequest,放入请求。为了错开发送请求(执行getRequest)的时间点,也用了sleep。

import java.util.Random;
public class ServeThread extends Thread {
   private final Random random;
   private final RequestQueue requestQueue;
   public ServeThread(RequestQueue requestQueue,String name,long seed){
       super(name);
       this.requestQueue=requestQueue;
       this.random=new Random(seed);
   }

   @Override
   public void run() {
       for (int i=0;i<10000;i++) {
           Request request=requestQueue.getRequest();
           System.out.println(Thread.currentThread().getName() + " handles  " + request);
           try {
               Thread.sleep(random.nextInt(1000));
           } catch (InterruptedException e) {
           }
       }
   }
}

Main类

Main类会首选创建RequestQueue的实例(requestQueue),然后分别创建名为Alice的实例ClientThread和名为Bobby的实例ServerThread,并将requestQueue传给这两个实例,最后执行start。
3141592L和6535897L只是用来作为随机数的种子,没有什么特殊含义。

public class Main {
   public static void main(String[] args) {
       requestQueue requestQueue=new requestQueue();
       new ClientThread(requestQueue,"Alice",3141592L).start();
       new ServeThread(requestQueue,"Bobby",6535897L).start();
   }
}

运行结果示例

Alice requests [RequestNO.0]
Bobby handles  [RequestNO.0]
Alice requests [RequestNO.1]
Alice requests [RequestNO.2]
Bobby handles  [RequestNO.1]
Bobby handles  [RequestNO.2]
Alice requests [RequestNO.3]
Bobby handles  [RequestNO.3]
Alice requests [RequestNO.4]
Bobby handles  [RequestNO.4]
Alice requests [RequestNO.5]
Alice requests [RequestNO.6]
Bobby handles  [RequestNO.5]

你可能感兴趣的:(java,多线程,设计模式,javaguard)