java多线程设计模式之GuardSuspension模式

来源:互联网 发布:淘宝围巾店推荐知乎 编辑:程序博客网 时间:2024/06/15 20:43

     Guard是被守护被保卫的意思,我们可以将GuardSuspension模式看作是singgleThreadExcution模式和一个判断条件所组成的,这么叙述太笼统,可以结合下面的案例来体会一下这个过程:

   我们模拟的是客户端向服务器端发送请求,服务器端做出反馈的例子,模拟的线程之间最简单的通信:

1.构建请求类:

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+" ]";}}
   其中使用到了上面讲述的immutable模式,这样可以保证name字段在不同的线程之间的唯一性。当有了请求类之后我们会想到请求会通过客户端不断地产生,假如服务器端的处理能力是有限的,那么怎么解决呢?我们可以设计一个队列,用于存放处于等待处理的request:
请求队列类:

public class RequestQueue {private final Queue<Request> queue = new LinkedList<Request>();public synchronized Request getRequest(){while(queue.peek() == null){try{wait();}catch(InterruptedException e){e.printStackTrace();}}return queue.remove();}public synchronized void putRequest(Request request){queue.offer(request);notifyAll();}}
请求队列中声明了一个链式列表用于储存等待的request类,并设计了getRequest()方法和putRequest()方法,并使用synchronized修饰,从而保证了对共享的实例操作的唯一性。其中在这个类中就是用到了这节课所说的设计模式,其中while后面的语句用于判断,意思就是如果等待队列中没有请求信息时,需要等待,,直到这里面有请求之后才弹出队列中的请求信息,传递给服务器端,我们把while后面的语句称作为守护条件,也就是说在原有的SigngleThreadExecuton模式中增加了一个守护条件,以保证业务逻辑和线程之间共享资源的安全。这样,reques类和RquestQueue类都是线程安全类。然后设计客户端,用来不断的发送信息:

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(InterruptedException e){e.printStackTrace();}}}}
    客户端类继承了Thread类,即在这个类中重写run()方法后执行新的线程来进行发送信息的任务,,其中run()方法中仅仅包含了一个循环体,循环体中循环产生request,同时将它放入requestqueue等待队列中,设计一个sleep()函数来模拟耗费时间。这个类构建时需要一个RequsetQueue类的实例,所以当我们传入这个实例时要保证这个类是线程安全的,我们设计的RequestQueue类符合这个要求。

   同时设计一个服务器端,用来处理消息:

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()+"handles "+request);try{Thread.sleep(random.nextInt(1000));}catch(InterruptedException e){e.printStackTrace();}}}}
    与客户端相同,我们也需要保证传入的等待队列是线程安全的,所以我们上面设计的RequestQueue类比较关键,起到了一个连接服务器端和客户端的通道的作用,而request请求则是通道中的传递物,服务器端同时启用一个新的线程来将星系打印出来,这同样需要继承Thread类。随后通过一个main函数来启动这个过程:

public class TestMain {public static void main(String[] args) {RequestQueue requestQueue = new RequestQueue();new ClientThread(requestQueue,"Alice",3145192L).start();new ServerThread(requestQueue,"Bobby",6535897L).start();}}
运行结果如下;


     当然我们也可以使用java.util.concurrent.LinkedBlockingQueue来重新设计RequestQueue类:

import java.util.concurrent.BlockingQueue;import java.util.concurrent.LinkedBlockingQueue;public class RequestQueues {private final BlockingQueue<Request> queue = new LinkedBlockingQueue<Request>();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){}}}

其中java.util.concurrent.LinkedBlockingQueue类的作用和上面设计的RequestQueue的作用是相同的,因为其实现了BlockingQueue接口,并且take和put方法都考虑了互斥处理,所以也就无需声明方法为synchronized修饰。

  同时可以修改下原来设计的RequestQueue类,了解一下wait()和nitfyAll方法的运行,是否符合之前所说的那样:

public class RequestQueue {private final Queue<Request> queue = new LinkedList<Request>();public synchronized Request getRequest(){while(queue.peek() == null){try{System.out.println(Thread.currentThread().getName() + ": wait() begins, queue = " + queue);wait();System.out.println(Thread.currentThread().getName() + ": wait() ends, queue = "+ queue);}catch(InterruptedException e){e.printStackTrace();}}return queue.remove();}public synchronized void putRequest(Request request){queue.offer(request);System.out.println(Thread.currentThread().getName()+": notifyAll() begins, queue = "+queue);notifyAll();System.out.println(Thread.currentThread().getName()+": notifyAll() ends, queue = "+queue);}}
执行结果如下:

                         
    针对下面的代码,考虑一下是否会出现问题?

public synchronized Request getRequest(){while(queue.peek() == null){try{System.out.println(Thread.currentThread().getName() + ": wait() begins, queue = " + queue);wait();System.out.println(Thread.currentThread().getName() + ": wait() ends, queue = "+ queue);}catch(InterruptedException e){e.printStackTrace();}}return queue.remove();}public synchronized void putRequest(Request request){queue.offer(request);System.out.println(Thread.currentThread().getName()+": notifyAll() begins, queue = "+queue);notifyAll();System.out.println(Thread.currentThread().getName()+": notifyAll() ends, queue = "+queue);}
(1)将getRequest方法中的while改为if:

   在本例中是不会发生问题的,但是在一般情况下还是存在着问题,假设多个线程都在wait()时,RequestQueue的实力被执行了notifyAll。这样以来多个线程就会同时运行,这时如果等待队列中只有一个request,第一个线程调用了queue.remove()之后,等待队列会变为空,queue.peek()的值变为null;但是第二个线程级之后开始的线程已请确认了之前的守护条件成立,那么即使是守护线程成立queue.remove()还是会被调用,因此正在wait()的线程在开始运行之前一定要检查守护条件,所以不应该使用if而是使用while。

(2)将synchronized修饰范围改为只修饰wait()方法。

   一般情况下还是会发生问题的,因为当程序运行到synchronized代码块外面时会检查条件和调用remove方法,当queue队列中仅有一个元素时,线程可能会抛出NoSuchElementException异常,同时,LinkedList本来就是非线程安全的,程序会欠缺安全性。

 (3)将while守护条件饭知道try...catch里面。

假设当线程正在wait()时,其他线程调用了Interrupt()方法,即时守护条件不成立,该线程也会跳出while循环语句,进入catch代码块,调用remove方法,也就是说,“等到守护条件满足”这一功能并没有实现。

(4)将wait()替换为Thread.Sleep

由于wait和Thread.Sleep()不同,直线wait()线程会释放对象实例的锁,而sleep()方法则不会,因此如果被synchronized修饰的getRequest方法中执行sleep()方法的话,那么其他线程,无论哪一个都无法进入putRequest方法或者getRequest方法,即已经陷入阻塞状态。由于无法进入putRequest方法,所以守护条件永远都不会成立,所以会重复的进行醒来-->检查-->休眠-->醒来的状态,线程一直处于阻塞状态。