Guarded Suspension模式

Guarded Suspension模式确保在满足特定条件时执行逻辑,否则线程等待。文章通过示例代码和时序图解释了模式的工作原理,强调了while和wait的使用,以及同步关键字在避免对象状态不一致中的作用。讨论了线程安全问题,包括wait与notifyAll的时机、if与while的区别、synchronized的作用域等,并提到了活锁问题。
摘要由CSDN通过智能技术生成

        好的,这一章我们继续介绍Guarded Suspension模式^_^。

        Guarded Suspension模式是在Single Threaded Execution模式基础上,只有满足守护条件的时候,才会执行相应的逻辑,否则当前线程进入对象等待队列。

        示例代码角色如下:

  1. 客户端线程ClientThread:往队列RequestQueue中添加数据;
  2. 服务端线程ServerThread:从队列RequestQueue中请求数据;
  3. 队列RequesuQueue:存放请求资源Request;
  4. Main:测试入口类。

        时序图如下所示:

        从时序图可以看到,当RequestQueue中存在Request资源时,ServerThread的getRequest请求可以立即执行;而RequestQueue中不存在资源时,ServerThread的getRequest请求将被阻塞,知道ClientThread执行putRequest方法后才能继续执行。(时序图中ClientThraed和ServerThread都被加粗,这是因为它们是主调对象;而RequestQueue是被调对象)

        话不多说,上代码:

public Class Request{
    private String name;
    public Request(String name){
        this.name = name;
    }
    public String getName(){
        return this.name;
    }
    @Override
    public String toString(){
        return "[Request " + name +" ]";
    }
}
public class RequestQueue{
    private final Queue<Request> queue = new LinkedList<Request>();
    
    public synchronized Request getRequest(){
        while(queue.peek() == null){
            try{
                wait();
            }catch(InterruptedException e){
                
            }
        }
        return queue.remove();
    }
    
    public synchronized void putRequest(Request request){
        queue.offer(quest);
        notifyAll();
    }
}
public class ClientThread extend 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);
            requestQueue.putRequest(request);
            System.out.println(Thread.currentThread.getName() + " put" +                             request.toString());
            try{
                Thread.sleep(random.nextInt(1000));
            }catch(InterruptedException e){
            }
        }
    }
}
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()" + " get" + request.toString());
            try{
                Thread.sleep(random.nextInt(1000));
            }catch(InterruptedException e){
                
            }
        }
    }
}
public class Main{
    public static void main(String[] args){
        RequestQueue requestQueue = new RequestQueue();
        new ClientThread(requestQueue, "producerA",3141592L).start();
        new ServerThread(requestQueue, "consumerA",6543872L).start();
    }
}

        例子中的守护条件是queue.peek()!=null,while内部的条件是守护条件的逻辑非。当守护条件不满足时,while之后的语句也不会执行。当某个线程执行wait方法,当前线程需持有当前对象的锁;当前对象执行notifyAll之后,等待队列中的线程退出等待能否获取CPU资源还需要进一步持有对象锁。这里需要注意的是,守护条件需要由while执行,而不能由if执行,因为使用while时,等待的线程往下执行的时候,会再次判断守护条件是否满足。

  1.  guardedMethod:由while和wait实现;
  2. stateChangingMethod:由notifyAll实现

       注意到Guarded Suspension模式使用了Synchronized关键字,使用线程同步的方式。那么为什么需要同步呢?因为需要避免对象状态的不一致。下述例子就是一个简单的不一致:

 LinkedList为线程不安全的类,而LinkedBlockQueue为线程安全的类,UML图如下:

 实现方式如下:

public class RequestQueue{
    private final Queue<Request> requestQueue = new LinkedBlockQueue<Request>();
    public Request getRequest(){
        try{
            Request request = requestQueue.take();
            return request;
        }catch(InterruptedException){
            
        }
    }
    
    public void putRequest(Request request){
        try{
            requestQueue.put(request);
        }catch(InterruptedException){
            
        }
    }
}

        Guarded Suspension模式包含Guarded Object(被守护的对象)。Guarded Object持有被守护的方法(Guarded Method)。当线程执行Guarded Method时,若守护条件成立,则可以立即执行;若守护条件不成立,则需要等待。同时Guarded Object中还需要有改变示例状态(守护条件)的方法(stateChangingMethod),否则线程可能一直处于等待队列中。

Tips:

        1.先执行notifyAll方法,再执行queue.offer(request)也是线程安全的,因为执行notifyAll之后,当前线程不会释放锁,因此等待队列中的线程也无法获得锁(也不会执行检查守护条件)。在执行offer方法之后,当前线程释放锁,阻塞线程之一获得锁,再继续执行(先检查守护条件);

        2.while如果改为if,因为执行notifyAll会唤醒所有的等待进程,如果queue中只有一个request被获得锁的进程消费之后,其他进程不会再检查守护条件,从而造成程序不安全;

        3.如果将synchronized只包含wait,那么while条件判断和remove可能会并发执行,从而造成不一致性;如下所示:

public Request getRequest(){
    while(queue.peek() == null){
        try{
            synchronized (this){
                wait();
            }
        }catch(InterruptedException e){
           
        }
    }
    return queue.remove();
}

          线程的执行顺序可能如下,从而造成程序异常:

        4.将try,catch挪到while的外面,如果当前等待的线程,其他线程调用了interrupt方法,则会直接到catch方法,不会再判断守护条件;   

        5.将wait替换为Thread.sleep,程序生存性问题会存在。如下所示:

public synchronized Request getRequest(){
    while(queue.peek() == null){
        try{
            Thread.sleep(100);
        }catch(InterruptedException e){
            
        }
    }
    return queue.remove();
}

        可能有同学会想到每隔100ms才去检查守护条件可能造成性能下降,当时这样写却能造成程序生存性的问题。因为sleep方法不会释放对象锁,而wait方法会释放对象锁。因此某个进程在getRequest中执行了sleep,那么其他进程会一直阻塞,setRequest方法也无法执行。类似于死循环,称之为活锁。线程在醒来->检查条件->休眠的过程中无限循环。

        好啦,关于Guarded Suspension设计模式我们就介绍这么多,下一章将介绍Balking模式,欢迎大家继续阅读。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值