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

     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方法,所以守护条件永远都不会成立,所以会重复的进行醒来-->检查-->休眠-->醒来的状态,线程一直处于阻塞状态。










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值