Guarded 是被守护,被保护的意思,Suspension 是暂停的意思。如果执行现在的处理会造成问题,就让执行处理的线程进行等待。
创建5个类
名字 | 说明 |
Request | 表示一个请求的类 |
RequestQueue | 依次存放请求的类 |
ClientThread | 发送请求的类 |
ServerThrad | 接受请求的类 |
Main | 测试类 |
/**
* 虽说是请求,由于只是用于表示clientThread传递给ServerThread的实例,所以不提供什么特殊的处理
*/
public class Request {
private final String name;
public Request(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Request{" +
"name='" + name + '\'' +
'}';
}
}
import java.util.LinkedList;
import java.util.Queue;
/**
* 此类用于依次存放请求,RequestQueue通过putRequest放入Request实例,并按顺序使用getRequest取出Request实例。
* 这种结构通常称为队列(queue)或者 FIFO(First In First Out ,先进先出)
*
*/
public class RequestQueue {
private final Queue<Request> queue = new LinkedList<>();
// 取出最先放在RequestQueue的中的一个请求,作为返回值,如果一个请求也没有就一直等待,直到其他线程执行putRequest.
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();
}
}
import java.util.Random;
/**
* 表示发送请求的线程。ClientThread持有RequestQueue的实例,并连续调用该实例的putRequest,放入请求。
*
*/
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 {
// 为了错开发送请求的执行点,使用java.util.Random类随机生成0-1000之间的数
Thread.sleep(random.nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.util.Random;
/**
* 用于表示接收请求的线程,该类也持有RuquestQueue的实例,ServerThread使用该实例的getRequst方法来接受请求。
*/
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);
}
@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) {
e.printStackTrace();
}
}
}
}
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();
}
}
运行结果:Alice源源不断的发送请求,而Bobby则会不停的处理请求。
在看RequestQuene类代码前,先看看
private final Queue<Request> queue = new LinkedList<>();
LinkedList类表示链表结构的集合。实例中用了该类的如下三个方法:
- Request remove() : 表示用于移除队列的第一元素,并返回该元素。如果队列中一个元素都没有,则会抛出异常
- boolean offer(Request req): 将元素req添加到队列末尾。
- Request peek(): 如果队列中存在元素,则该方法会返回头元素,如果为空,则返回null。该方法不移除元素。
LinkedList是线程不安全的,java.util.concurrent.LinkedBlockingQueue是线程安全的。
先看看获取请求
public synchronized Request getRequest() { while (queue.peek() == null) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } return queue.remove(); }
想要获取个请求 ,那这个请求必须存在,如果没有请求,那你就给我等着,别动不动就来要请求。
queue.peek() != null 即为守护条件。满足才能 queue.remove();
while() 里面就是守护条件的逻辑非判断,如果满足,不好意思。只能wait!!!
再看看添加请求
public synchronized void putRequest(Request request){ queue.offer(request); notifyAll(); }
执行 queue.offer(request)就是向队列中的尾部添加一个请求。
这时队列中至少存在一个可取出的元素,因此可以在上一个方法中可以成功取出请求,就别让他等了,赶紧notifyAll()唤醒吧!
当使用synchronized关键字的时候你要想清楚它需要保护什么,在这里它保护的就是这个队列的实例queue,而这两个方法对队列的操作()应该保证同一时刻只有一个线程执行。
这里提醒一点,某个线程执行某个实例的wait方法,这时线程必须获取该实例的锁,上面调用wait时,其实就已经提前获取到了this对象的锁。线程执行this的wait方法后。进入this的等待队列,并释放持有的this锁。
notify、notifyAll、interrupt会让线程退出等待队列,但是在实际地继续执行处理之前,该线程也还会检查守护条件。不管被notify/notifyAll多少次,如果守护条件不成立,线程都会随着while再次wait(还必须再获取this的锁)
Guarded Suspension 模式中的登场角色
GuardedObject(被守护的对象)
GuardedObject角色是一个持有被守护的方法(guardedMethod)的类,当线程执行guardedMethod方法时,若守护条件成功,则可以立即执行操作;不成立时,就要进行等待。除了guardedMethod方法,GuardedObject角色还应该有持有其他改变实例状态的方法(stateChangingMethod)。stateChangingMethod通过notify/notifyAll方法来实现,guardedMethod通过while语句和wait方法来实现。
Guarded Suspension 模式的特点是:
- 存在循环
- 存在条件检查
- 因为某种原因而等待
重构:使用java.util.concurrent.LinkedBlocklingQueue替换RequestQueue类中的操作方式
LinkedBlocklingQueue是线程安全的类,因此重构后的代码如下:
public class RequestQueue {
private final BlockingQueue<Request> queue = new LinkedBlockingQueue<>();
// 取出最先放在RequestQueue的中的一个请求,作为返回值,如果一个请求也没有就一直等待,直到其他线程执行putRequest.
public Request getRequest() {
Request request = null;
try {
request = queue.take(); // 取出队首元素,如果队列为空,会进行等待
} catch (InterruptedException e) {
e.printStackTrace();
}
return request;
}
// 添加一个请求
public void putRequest(Request request){
try {
queue.put(request); // 向队尾添加元素
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}