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