Guarded Suspension 保护性暂定模式 以及嵌套死锁问题

多线程交互时,满足条件才去执行,否则阻塞一直到满足条件。当然可以用wait/notify实现。
本文用JUC包下的reentrantlock和其条件变量来完成。

首先定义Predicate 和GuardAction;

Predicate:保护条件。只有保护条件为真(或为假),才去真正执行目标动作。
GuardAction : 抽象了目标动作(里面的call()方法),并关联了目标动作所需的保护条件(predicate)。意思是,如果predicate为真,才去调用call(),否者就去阻塞。当然阻塞和唤醒都是通过Blocker实现的。

interface Predicate {
    boolean evaluate();
}

abstract class GuardAction<V> implements Callable<V> {
    protected final Predicate guard;
    public GuardAction(Predicate guard) {
        this.guard = guard;
    }
}

然后定义Blocker

它的作用是判断保护条件并阻塞调用的线程;唤醒阻塞的线程

interface Blocker {
    /**
     * 在保护条件成立时立即执行目标动作。
     * 否则阻塞当前线程,直到保护条件成立
     */
    <V> V callWithGuard(GuardAction<V> guardAction) throws Exception;

    /**
     * 在执行stateOperation所指定的操作后,决定是否唤醒本Blocker所暂挂的所有线程中的一个
     */
    void signalAfter(Callable<Boolean> stateOperation) throws Exception;

    void signal() throws InterruptedException;

    /**
     * 在执行stateOperation所指定的操作后,决定是否唤醒本Blocker所暂挂的所有线程
     */
    void broadcastAfter(Callable<Boolean> stateOperation) throws Exception;
}

@Slf4j
class ConditionVarBlocker implements Blocker {
    private final Lock lock;
    private final Condition condition;
    private final boolean allowAccess2Lock;

    public ConditionVarBlocker(Lock lock) {
        this(lock, true);
    }
    public ConditionVarBlocker(Lock lock, boolean flag) {
        this.lock = lock;
        this.allowAccess2Lock = flag;
        this.condition = lock.newCondition();
    }
    public ConditionVarBlocker() {
        this(false);
    }
    public ConditionVarBlocker(boolean flag) {
        this(new ReentrantLock(), flag);
    }

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

    @Override
    public <V> V callWithGuard(GuardAction<V> guardAction) throws Exception {
        lock.lockInterruptibly();
        V result;
        try {
            final Predicate guard = guardAction.guard;
            while (!guard.evaluate()) {
                log.info("waiting...");
                condition.await();
            }
            result = guardAction.call();
            return result;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public void signalAfter(Callable<Boolean> 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<Boolean> stateOperation) throws Exception {
        lock.lockInterruptibly();
        try {
            if (stateOperation.call()) {
                condition.signalAll();
            }
        }finally {
            lock.unlock();
        }
    }
}

如何使用

AlarmAgent是给告警服务器发送告警消息的。调用其sendAlarm()就可以发送。
但是必须与告警服务器建立了连接后才能发送。
若连接未建立(或者连接中断)​,sendAlarm()的执行线程应该被暂挂直到连接建立完毕(或者恢复)​。

  1. 首先我们定义好什么是需要保护的条件Predicate:是否与服务器建立了连接
  2. 然后定义好此条件为true时,我们需要做什么。也就是写好guardAction:发送消息给服务器
  3. 通过Blocker调用

按照上面的方法,写出的代码如下:

class AlarmAgentDemo {

    private volatile boolean connectedToServer = false;

    private final Predicate predicate = new Predicate() {
        @Override
        public boolean evaluate() {
            return connectedToServer;
        }
    };

    private final GuardAction<Void> guardAction = new GuardAction<Void>(predicate) {
        @Override
        public Void call() throws Exception {
            // 发送告警日志给服务器
            return null;
        }
    };
    private final Blocker blocker = new ConditionVarBlocker();

    public void sendAlarm() {
        // 当前main线程就是执行操作的线程。如果连接到了服务器,就直接发送;否者就去阻塞
        try {
            blocker.callWithGuard(guardAction);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    
    public static void main(String[] args) {
        AlarmAgentDemo alarmAgentDemo = new AlarmAgentDemo();
        alarmAgentDemo.sendAlarm();
    }

}

继续完善:
4. 和服务器连接的代码:连接成功,就修改状态
5. 失连后重连的代码:搞一个定时任务(心跳),轮询步骤4的代码。

public void init() {
        // 1.连接服务器
        connectServer();

        // 2.开启定时任务监控与服务器的连接状态
        new Timer(true).schedule(new TimerTask() {
            @Override
            public void run() {
                if(!testConnection()) {
                    // 失连后,重新连接
                    connectedToServer = false;
                    connectServer();
                    // 唤醒被阻塞的线程
                    try {
                        blocker.broadcastAfter(() -> {
                            return connectedToServer;
                        });
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }

            // 测试与服务器的连接状态
            private boolean testConnection() {
                return new Random().nextBoolean();
            }
        }, 60000, 2000);
    }

    private void connectServer() {
        // 模拟连接
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 连接成功
        connectedToServer = true;
    }

完整代码如下

@Slf4j
class AlarmAgentDemo {

    private volatile boolean connectedToServer = false;

    private final Predicate predicate = new Predicate() {
        @Override
        public boolean evaluate() {
            return connectedToServer;
        }
    };

    private final GuardAction<Void> guardAction = new GuardAction<Void>(predicate) {
        @Override
        public Void call() throws Exception {
            // 发送告警日志给服务器
            log.info("send message to server success");
            return null;
        }
    };
    private final Blocker blocker = new ConditionVarBlocker();

    public void sendAlarm() {
        // 当前main线程就是执行操作的线程。如果连接到了服务器,就直接发送;否者就去阻塞
        try {
            blocker.callWithGuard(guardAction);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void init() {
        // 1.连接服务器
        connectServer();

        // 2.开启定时任务监控与服务器的连接状态
        new Timer(true).schedule(new TimerTask() {
            @Override
            public void run() {
                if(!testConnection()) {
                    log.info("失连,重连服务器中...");
                    // 失连后,重新连接
                    connectedToServer = false;
                    connectServer();
                    // 唤醒被阻塞的线程
                    try {
                        blocker.broadcastAfter(() -> {
                            return connectedToServer;
                        });
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else {
                    log.info("与服务器正常连接中");
                }
            }

            // 测试与服务器的连接状态
            private boolean testConnection() {
                return new Random().nextBoolean();
            }
        }, 6000, 2000);
    }

    private void connectServer() {
        // 模拟连接
        try {
            Thread.sleep(new Random().nextInt(50000));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.info("connect server success");
        // 连接成功
        connectedToServer = true;
    }

    public static void main(String[] args) {
        AlarmAgentDemo alarmAgentDemo = new AlarmAgentDemo();
        alarmAgentDemo.init();
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(new Random().nextInt(1000));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            alarmAgentDemo.sendAlarm();
        }
    }

}

嵌套死锁问题

文末最后讨论了一个嵌套死锁问题。
代码大概是这样的:
这两个方法在不同的线程中运行,可能导致死锁。
因为方法1可能会在条件变量上await(),释放了lock锁。但是外层的synchronized锁没有释放。导致方法2一直被阻塞。而方法1又需要方法2通知才能继续运行。
所以在上面的block初始化时,可以传入一个lock锁实例,这样就可以避免了锁嵌套问题。
ps: synchronized释放锁的条件:代码执行完、抛出异常、调用了wait方法。

方法1synchronized (obj) {
	blocker.callWithGuard(); //
}

方法2synchronized (obj) {
	blocker.signalAfter(); //
}

原文代码:
在这里插入图片描述
在这里插入图片描述

本文作者:WKP9418
原文地址:https://blog.csdn.net/qq_43179428/article/details/141760448

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值