实时通知队列的设计与实现

问题分析

在程序设计中,我们经常遇到这样的问题:

有一个模块,外部多线程访问,其内部也有多个工作线程;外部调用和内部工作都可能产生一系列事件(包括状态变化),这些都需要“有序”并且“实时”的通知到外部。

总结一下基本需求:

  • 多路,事件来源于多个线程
  • 有序,事件通知要严格保持其产生的顺序
  • 实时,外部收到状态变化(一种事件)通知时,可以假定模块仍然处在该状态
  • 串行,外部不需要在回调方法中显示同步,“有序”其实隐含串行的要求

考虑一下实时的意义。比如模块是用来串行完成一个个任务的,每个任务执行中会通知外部任务状态的变化。外部在收到某个状态变化时,会去获取当前任务的部分中间结果。如果通知不是实时的,也可能从通知发出到收到这之间,模块已经执行的下一个任务了,那么外部也可能获取的是一个任务的中间结果,从而导致错误的处理。

上面这个问题可以有其他方式解决,比如获取中间结果时带上任务ID,但是本文还是想纯粹从“事件通知”这个问题角度做一些探讨。

为此我们设计这样一种通知机制:

  • 通知队列,多线程产生的事件,需要在队列中排序,满足“有序”的需求
  • 当即通知,每个方法(包括内部线程和外部调用的方法)在退出前,都触发事件的分派(即针对每个事件,调用回调方法)
  • 独占分派,当一个线程正在分派一个事件时(调用回调方法),其他线程不能分派事件,满足“串行”需求

针对没有抢到分派权时,如何处理,有两种策略:

  • 退出分派

因为肯定有其他线程会继续分派剩余事情,所以不需要担心当前线程产生的事情滞留在队列中(假设此后没有触发新的分派,可能导致滞留);但是损失了部分实时性

  • 继续等待

等待并尝试分派后续事件,这种策略能够提高实时性

其他设计:

  • 不在加锁状态下分派事件:考虑外部会在事件回调中,调用模块的方法,可能导致死锁
  • 检测分派重入情形:还是上面的情况,当前线程再次进入模块事件分派方法,可以检测并忽略(直接退出分派方法)
  • 内部锁递归检测:考虑模块内部方法A加锁调用公开方法B,B需要在退出前分派事件,这就会在加锁状态下分派事件,所以需要检测内部锁是否lock

队列的实现

内部变量包括:

  • 一个先入先出的队列
  • 当前在分派的线程
  • 一个互斥锁
  • 一个条件变量

方法:

push:加入事件,多线程调用,需要互斥锁保护;加入事件不通知分派,因为没有固定的分派线程

poll:分派事件,步骤:

  1. lock
  2. 检查分派线程是否为NULL,非空退出(也可以不退出,等待策略;但是如果等于当前线程,仍然退出)
  3. 等待分派线程变为NULL,通过条件变量循环等待
  4. 如果事件队列为空,退出
  5. 取出一个事件,设置分派线程为当前线程
  6. unlock
  7. 调用回调
  8. lock
  9. 设置分派线程为NULL
  10. 唤醒条件变量
  11. unlock
  12. 循环到第1步(这里其他分派线程可以进入,抢夺分派权)

附Java实现类

package xxx;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.List;


public class NotifyQueue<Listener> {

    private static final String TAG = "NotifyQueue";

    private List<Object> mPendingNotifies = new ArrayList<Object>();
    private Thread mNotifyThread;
    private Listener mNoopListener;
    private List<Listener> mListeners = new ArrayList<Listener>();
    private Listener[] mListenerCopy;
    private int mListenerCopyCount = 0;
    private Object mLock = new Object();
    
    @SuppressWarnings("unchecked")
    NotifyQueue(Listener noop) {
        mNoopListener = noop;
        mListenerCopy = (Listener[]) Array.newInstance(noop.getClass().getInterfaces()[0], 4);
    }
    
    public void addListener(Listener listener) {
        synchronized (mLock) {
            if (!mListeners.contains(listener))
                mListeners.add(listener);
        }
    }

    public void removeListener(Listener listener) {
        removeListener(listener, true);
    }
    
    public void removeListener(Listener listener, boolean wait) {
        synchronized (mLock) {
            if (!mListeners.remove(listener))
                return;
            if (mListenerCopyCount == 0) { // mNotifyThread is not null when locked
                return;
            }
            // make sure that the listener is not using
            if (mNotifyThread == Thread.currentThread()) {
                for (int i = 0; i < mListenerCopyCount; ++i) {
                    if (mListenerCopy[i] == listener)
                        mListenerCopy[i] = mNoopListener;
                }
            } else if (wait) {
                while (mNotifyThread != null) {
                    try {
                        mLock.wait();
                    } catch (InterruptedException e) {
                        Log.w(TAG, "removeTaskStateChangeListener", e);
                    }
                }
            }
        }
    }
        
    public void push(Object notify) {
        synchronized (mLock) {
            mPendingNotifies.add(notify);
        }
    }
    
    /*
     * try to dispatch notifies, 
     * exit soon if another thread is dispatching
     */
    public void poll() {
        poll(false);
    }
    
    /*
     * wait for all notifies to be dispatched, 
     * not matter dispatch by self or another threads
     */
    public void sync() {
        poll(true);
    }

    protected void poll(boolean sync) {
        Object notify = null;
        while (true) {
            synchronized (mLock) {
                if (mNotifyThread != null) {
                    if (!sync) return;
                    while (mNotifyThread != null) {
                        try {
                            mLock.wait();
                        } catch (InterruptedException e) {
                            Log.w(TAG, "poll", e);
                        }
                    }
                }
                if (mPendingNotifies.isEmpty())
                    return;
                mNotifyThread = Thread.currentThread();
                mListenerCopy = mListeners.toArray(mListenerCopy);
                mListenerCopyCount = mListeners.size();
                notify = mPendingNotifies.remove(0);
            }
            invoke(mListenerCopy, mListenerCopyCount, notify);
            synchronized (mLock) {
                // not clear mListenerCopy for optimize
                mListenerCopyCount = 0;
                mNotifyThread = null;
                mLock.notifyAll();
            }
        }
    }

    protected boolean lock(Thread lockThread) {
        Log.d(TAG, "lock");
        synchronized (mLock) {
            if (mNotifyThread == null) {
                mNotifyThread = lockThread;
                return true;
            } else {
                return false;
            }
        }
    }

    protected boolean unlock(Thread lockThread) {
        Log.d(TAG, "unlock");
        synchronized (mLock) {
            if (mNotifyThread == lockThread) {
                mNotifyThread = null;
                mLock.notifyAll();
            } else {
                return false;
            }
        }
        poll();
        return true;
    }

    protected void invoke(Listener[] listeners, int count, Object notify) {
        if (notify instanceof Runnable) {
            Runnable runnable = (Runnable) notify;
            runnable.run();
        }
    }

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fighting Horse

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值