多线程之Guarded Suspension 模式

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类表示链表结构的集合。实例中用了该类的如下三个方法:
  1. Request remove() : 表示用于移除队列的第一元素,并返回该元素。如果队列中一个元素都没有,则会抛出异常
  2. boolean offer(Request req): 将元素req添加到队列末尾。
  3. 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();
        }
    }
}

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值