guarded suspension-要等我准备好呦

多线程 guarded suspension 模式

在java多线程设计模式这本书中,提及到guarded suspension模式。
这种模式应用在”你要等我准备好哦”这种情况下。表明某个状态正在被保护着,不能对其进行操作。

举个🌰,模拟一个http服务器的交互
1.客户端提交request到服务端
2.服务端接受到request放在requestQueue中
3.服务端处理request

首先需要一个RequestQueue,用来存放request对象,这个队列同时被客户端和服务端操作。

public class RequestQueue {

    private final LinkedList<Request> queue = new LinkedList();

    public synchronized Request getRequest(){
        while(queue.size() <= 0){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return queue.removeFirst();
    }

    public synchronized void putRequest(Request request){
        queue.addLast(request);
        notifyAll();
    }

}

然后是ClientThread向队列中put request对象

public class ClientThread extends Thread {

    private Random random;
    private final RequestQueue requestQueue;

    public ClientThread(RequestQueue requestQueue, String name, long seed) {
        super(name);
        this.random = new Random(seed);
        this.requestQueue = requestQueue;
    }

    @Override
    public void run() {

        int requestNum = 10000;
        for (int i = 0; i < requestNum; i++){
            Request request = new Request("No." + i);
            log(Thread.currentThread().getName() + " requests " + request);
            requestQueue.putRequest(request);
            doSomeThing();
        }

    }

    private void log(String log){
        System.out.println(log);
    }

    private void doSomeThing(){
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

ServerThread从队列中拿出request进行处理

public class ServerThread extends Thread {

    private Random random;
    private final RequestQueue requestQueue;

    public ServerThread(RequestQueue requestQueue, String name, long seed) {
        super(name);
        this.random = new Random(seed);
        this.requestQueue = requestQueue;
    }

    @Override
    public void run() {

        int requestNum = 10000;
        for (int i = 0; i < requestNum; i++) {
            Request request = requestQueue.getRequest();
            log(Thread.currentThread().getName() + " handles " + request);
            doSomeThing();
        }

    }

    private void log(String log) {
        System.out.println(log);
    }

    private void doSomeThing() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

Main 中执行完整的运行

public class Main {

    public static void main(String[] args){
        RequestQueue requestQueue = new RequestQueue();
        new ClientThread(requestQueue,"client thread",2323423l).start();
        new ServerThread(requestQueue,"server thread-demo1",9292822l).start();
        new ServerThread(requestQueue,"server thread-2",9292822l).start();
        new ServerThread(requestQueue,"server thread-3",9292822l).start();
        new ServerThread(requestQueue,"server thread-4",9292822l).start();
        new ServerThread(requestQueue,"server thread-5",9292822l).start();
    }
}

RequestQueue里封装了一个LinkedList对象放出request对象
getRequest方法,从queue队列头中获取一个request对象,getRequest方法必须在queue中值时才能正确的执行。

一般这种情况都是有两种模式轮训和事件通知,很明显的是基于轮训的方式无端的消耗着cpu时间片。所以使用后者,guarded suspension 模式也是基于此实现的。

目的:从queue中取得request
条件:queue.size() > 0

使用synchronized,wait方式,首先若是不满足条件进入while,调用wait(),线程进入到对象的wait set中,等待其他线程对该对象notify

其他线程在什么情况下对其notify呢?看条件变量queue.size() > 0,queue的size大于0时,也就是putRequest的时候

现在已经知道什么时候去wait,什么时候去notify了。
可为什么要加synchronized同步呢,这个以后再进行详细说明。简单介绍下
在线程调用object.wait方法时候,会把当前线程防到wait set中(虚拟概念),并且该线程进入Wait状态
在线程调用object.notify方法的时候,会把object的 wait set中的一个线程唤醒
多个线程同时处理共享的变量域,所以需要加synchronized同步。

现在回来,当其他线程调用putRequest的时候,并且同步锁释放了,之前调用因getRequest方法处于Wait状态的线程获取到了锁,进行后续操作,好有问题!!!看代码

public synchronized Request getRequest(){
    while(queue.size() <= 0){
        try {
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    return queue.removeFirst();
}

synchronized方法并没有执行完,不应该释放同步锁,为什么putRequest方法可以获取到锁,并且执行?

stackoverflow原文

“Invoking wait inside a synchronized method is a simple way to acquire the intrinsic lock”

This sentence is false, it is an error in documentation.

Thread acquires the intrinsic lock when it enters a synchronized method. Thread inside the synchronized method is set as the owner of the lock and is in RUNNABLE state. Any thread that attempts to enter the locked method becomes BLOCKED.

When thread calls wait it releases the current object lock (it keeps all locks from other objects) and than goes to WAITING state.

When some other thread calls notify or notifyAll on that same object the first thread changes state from WAITING to BLOCKED, Notified thread does NOT automatically reacquire the lock or become RUNNABLE, in fact it must fight for the lock with all other blocked threads.

WAITING and BLOCKED states both prevent thread from running, but they are very different.

WAITING threads must be explicitly transformed to BLOCKED threads by a notify from some other thread.

WAITING never goes directly to RUNNABLE.

When RUNNABLE thread releases the lock (by leaving monitor or by waiting) one of BLOCKED threads automatically takes its place.

So to summarize, thread acquires the lock when it enters synchronized method or when it reenters the synchronized method after the wait.

public synchronized guardedJoy() {
    // must get lock before entering here
    while(!joy) {
        try {
            wait(); // releases lock here
            // must regain the lock to reentering here
        } catch (InterruptedException e) {}
    }
    System.out.println("Joy and efficiency have been achieved!");
}

也就是说进入guardedJoy方法时候需要获取到锁
当调用wait方法时会释放锁,线程从Runnable进入Wait
当其他线程调用notify
线程从Wait进入Blocking
这时候需要获取到同步锁才能进行Runnable
明白了

整理一下guarded suspension 模式

1.一个具有状态的对象,这个对象被多个线程同时访问,这个对象只有在自己的状态合适的时候,才会让线程进行某些处理
2.使用警戒条件来表示对象的状态,并且在目的处理前,测试满足条件不满足则进行wait,满足则执行处理方法
3.用notify方法通知wait中的线程表明状态已经满足了,wait中的线程进入blocking获得锁后执行操作

参考代码:

https://github.com/whx4J8/Thread.git

参考文献:

java多线程设计模式第3章
stackoverflow : http://stackoverflow.com/questions/13249835/java-does-wait-release-lock-from-synchronized-block