多线程设计模式-保护性暂挂模式

定义:如果某个线程执行特定的操作前需要满足一定的条件,则在该条件未满足时将该线程暂停运行(即暂挂线程,使其处于等待状态,直到该条件满足时才继续该线程的运行)

 

多线程设计模式-保护性暂挂模式_第1张图片 保护性暂挂模式UML图

GuardedObject: 包含受保护方法的对象
    guardedMethod: 受保护方法
    stateChanged: 改变GuardedObject实例状态的方法。该方法负责在保护条件成立时唤醒受保护方法的执行线程
GuardedAction: 抽象了目标动作,并关联了目标动作所需的保护条件
    call: 用于表示目标动作的方法
ConcreateGuardedAction: 应用程序所实现的具体目标动作及其关联的保护条件
Predicate: 抽象了保护条件
    evaluate: 用于表示保护条件的方法
ConcretePredicate: 应用程序所实现的具体保护条件
Blocker;负责对执行guardedMethod方法的线程进行挂起和唤醒,并执行ConcreteGuardedAction所实现的目标操作
    callWithGuard: 负责执行目标操作和暂挂当前线程
    signalAfter: 负责执行其参数指定的动作和唤醒由该方法所属Blocker实例所暂挂的线程中的一个线程
    signal: 负责唤醒由该方法所属Blocker实例所暂挂的线程中的一个线程
    broadcast: 负责唤醒由该方法所属Blocker实例暂挂的所有线程
ConditionVarBlocker: 基于Java条件变量(java.util.concurrent.locks.Condition)实现的Blocker

下列代码中AlarmAgent#sendAlarm()负责将信息发送到服务器,但是由于网络连接可能会出现问题(中断或者未连接等情况),执行这个方法的线程就会暂挂直到被唤醒(即连接成功建立)。实现的方法是设立连接标志connectedToServer,当标志被设为true,即AlarmAgent#onConnected()$signalAfter()就会调用condition.signal()方法唤醒暂挂线程。这里利用Condition的特性来实现暂挂(等待)和唤醒。

package com.bruce.guardedSuspension;

/**
* @Author: Bruce
* @Date: 2019/5/30 21:21
* @Version 1.0
*/
public interface Predicate {

    boolean evaluate();
}
package com.bruce.guardedSuspension;

import java.util.concurrent.Callable;

/**
* @Author: Bruce
* @Date: 2019/5/30 21:21
* @Version 1.0
*/
public interface Blocker {

     V callWithGuarded(GuardedAction guardedAction) throws Exception;

    void signalAfter(Callable stateOperation) throws Exception;

    void signal() throws InterruptedException;

    void broadcastAfter(Callable stateOperation) throws Exception;
}
package com.bruce.guardedSuspension;

/**
* @Author: Bruce
* @Date: 2019/5/30 22:04
* @Version 1.0
*/
public enum AlarmType {

    FAULT("fault"), RESUME("resume");

    private final String name;

    AlarmType(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }
}
package com.bruce.guardedSuspension;

import java.util.Objects;

/**
* @Author: Bruce
* @Date: 2019/5/30 22:03
* @Version 1.0
*/
public class AlarmInfo {

    private String id;
    private String extraInfo;
    public final AlarmType type;

    public AlarmInfo(String id, AlarmType type) {
        this.id = id;
        this.type = type;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getExtraInfo() {
        return extraInfo;
    }

    public void setExtraInfo(String extraInfo) {
        this.extraInfo = extraInfo;
    }

    @Override
    public String toString() {
        return "AlarmInfo{" +
                "id='" + id + '\'' +
                ", extraInfo='" + extraInfo + '\'' +
                ", type=" + type +
                '}';
    }

    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
          return true;
      }
      if (obj == null) {
          return false;
      }
      if (getClass() != obj.getClass()) {
          return false;
      }
      AlarmInfo other = (AlarmInfo) obj;
      if (extraInfo == null) {
          if (other.extraInfo != null) {
              return false;
          } else if (!extraInfo.equals(other.extraInfo)) {
              return false;
          }
          if (id == null) {
              if (other.id != null) {
                  return false;
              } else if (!id.equals(other.id)) {
                  return false;
              }
          }
          if (type != other.type) {
              return false;
          }
      }
        return true;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result
                + ((extraInfo == null) ? 0 : extraInfo.hashCode());
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        result = prime * result + ((type == null) ? 0 : type.hashCode());
        return result;
    }
}
package com.bruce.guardedSuspension;

import java.util.concurrent.Callable;

/**
* @Author: Bruce
* @Date: 2019/5/30 21:24
* @Version 1.0
*/
public abstract class GuardedAction implements Callable {

    protected final Predicate guard;

    public GuardedAction(Predicate guard) {
        this.guard = guard;
    }
}
package com.bruce.guardedSuspension;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.Callable;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* @Author: Bruce
* @Date: 2019/5/30 23:08
* @Version 1.0
*/
public class ConditionVarBlocker implements Blocker {

    private Logger LOG = LoggerFactory.getLogger(ConditionVarBlocker.class);
    private final Lock lock;
    private final Condition condition;
    private final boolean allowAccess2Lock;

    public ConditionVarBlocker(Lock lock) {
        this(lock, true);
    }

    private ConditionVarBlocker(Lock lock, boolean allowAccess2Lock) {
        this.lock = lock;
        this.allowAccess2Lock = allowAccess2Lock;
        this.condition = lock.newCondition();
    }

    public ConditionVarBlocker() {
        this(false);
    }

    public ConditionVarBlocker(boolean allowAccess2Lock) {
        this(new ReentrantLock(), allowAccess2Lock);
    }

    public Lock getLock() {
        if (allowAccess2Lock) {
            return this.lock;
        }
        throw new IllegalStateException("Access to the lock disallowed.");
    }

    @Override
    public  V callWithGuarded(GuardedAction guardedAction) throws Exception {
        lock.lockInterruptibly();
        V result;
        try {
            final Predicate guard = guardedAction.guard;
            while (!guard.evaluate()) {
                LOG.info("waiting...");
                condition.await();
            }
            result = guardedAction.call();
            return result;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void signalAfter(Callable stateOperation) throws Exception {
        lock.lockInterruptibly();
        try {
            if (stateOperation.call()) {
                condition.signal();
            }
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void signal() throws InterruptedException {
        lock.lockInterruptibly();
        try {
            condition.signal();
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void broadcastAfter(Callable stateOperation) throws Exception {
        lock.lockInterruptibly();
        try {
            if (stateOperation.call()) {
                condition.signalAll();
            }
        } finally {
            lock.unlock();
        }
    }
}
package com.bruce.guardedSuspension;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.Callable;

/**
* @Author: Bruce
* @Date: 2019/5/30 21:20
* @Version 1.0
*/
public class AlarmAgent {

    public Logger LOG = LoggerFactory.getLogger(AlarmAgent.class);

    private volatile boolean connectedToServer = false;

    private final Predicate agentConnected = new Predicate() {

        @Override
        public boolean evaluate() {
            return connectedToServer;
        }
    };

    private final Blocker blocker = new ConditionVarBlocker();

    private final Timer heartbeatTimer = new Timer(true);

    public void sendAlarm(final AlarmInfo alarm) throws Exception {
        GuardedAction guardedAction = new GuardedAction(agentConnected) {
            @Override
            public Void call() throws Exception {
                doSendAlarm(alarm);
                return null;
            }
        };
        blocker.callWithGuarded(guardedAction);
    }

    private void doSendAlarm(AlarmInfo alarm) {
        LOG.info("sending alarm " + alarm);

        try {
            System.out.println("Sending alarm...");
            Thread.sleep(50);
        } catch (Exception e) {
        }
    }


    public void init() {

        System.out.println("initing...");

        Thread connectingThread = new Thread(new ConnectingTask());

        connectingThread.start();

        heartbeatTimer.schedule(new HeartbeatTask(), 60000, 2000);
    }

    public void disconnect() {
        LOG.info("disconnected from alarm server.");
        connectedToServer = false;
    }

    protected void onConnected() {
        try {
            blocker.signalAfter(new Callable() {
                @Override
                public Boolean call() throws Exception {
                    connectedToServer = true;
                    LOG.info("connected to server");
                    return Boolean.TRUE;
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    protected void onDisconnected() {
        connectedToServer = false;
    }

    private class ConnectingTask implements Runnable {

        @Override
        public void run() {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            onConnected();
        }
    }

    private class HeartbeatTask extends TimerTask {

        @Override
        public void run() {
            if (!testConnection()) {
                onDisconnected();
                reconnect();
            }
        }

        private boolean testConnection() {
            return true;
        }

        private void reconnect() {
            ConnectingTask connectingTask = new ConnectingTask();
            connectingTask.run();
        }
    }
}

详细源码可见github

Blocker实现类中几个易错的技术细节:
1. 内存可见性和锁泄露(Lock Leak)
2. 线程过早被唤醒
3. 嵌套监视器锁死

java.util.concurrent.LinkedBlockingQueue就使用了保护性暂挂模式,take方法用于从队列中取出一个元素,如果take方法被调用时,队列是空的,则当前线程会被阻塞,直到队列不为空时,该方法才返回一个出队列的元素。其实在LinkedBlockingQueue中,也是通过Condition的特性在执行方法take()和put()时进行阻塞和唤醒,感兴趣可以阅读文章【死磕Java并发】—–J.U.C之阻塞队列:LinkedBlockingDeque, 虽然文章讲的是Deque,但实现原理是一样的。

 

参考资料

黄文海 Java多线程编程实战指南(设计模式篇)

黄文海的Github

推荐阅读

【死磕Java并发】—–J.U.C之阻塞队列:LinkedBlockingDeque

你可能感兴趣的:(多线程设计模式)