Java的并发库发出信号并等待并发的状态变化

64 阅读4分钟

信号和等待并发的状态变化

Java的并发库*(java.util.concurrent*)提供了一个叫做ReentrantLock的互斥(mutex)。这个锁维护着一个等待拥有该锁的线程队列,允许访问受保护的资源。一个线程可以通过调用lock()被添加到锁的等待队列中。当lock()方法返回时,该线程将拥有该锁。一旦线程以这种方式获得了锁,它就可以突变任何受锁保护的共享状态,然后它可以通过调用unlock()释放其所有权,允许另一个线程轮流拥有锁和访问共享状态。因为锁是可重入的,一个线程可以多次调用lock(),只有当所有对lock()的嵌套调用都被unlock()撤销时,锁才会被释放给下一个等待的线程。一个使用锁的可重入线程的流程看起来像这样。

lock() 
    lock() 
        lock() 
        unlock()
    unlock()
unlock()

KivaKit为这个功能提供了一个简单的扩展,它减少了对*lock()unlock()*的模板调用,并确保所有的lock调用都被unlock调用所平衡:

public class Lock extends ReentrantLock
{
    /**
     * Runs the provided code while holding this lock.
     */
    public void whileLocked(Runnable code)
    {
        lock();
        try
        {
            code.run();
        }
        finally
        {
            unlock();
        }
    }
}

使用这个类看起来像:

private Lock lock = new Lock();

[...]

lock.whileLocked(() -> mutateSharedState());

除了互斥之外,ReentrantLock(事实上,所有的JavaLock实现)为一个线程提供了一种简单的方法来等待另一个线程的信号。这种行为使得ReentrantLock成为一个条件锁,正如Java的Lock接口所声明的那样:

public interface Lock
{
    void lock();
    void unlock();
    Condition newCondition();
}

newCondition返回的Condition实现,为拥有锁的线程提供了信号或等待条件的方法(类似于Java监控)。对Condition接口的简化看起来是这样的:

public interface Condition
{
    void await() throws InterruptedException;
    void signal();
}

KivaKit使用条件锁来实现StateWatcher,它提供了一种对特定状态发出信号和等待的方式。

比如说:

enum State
{
    IDLE,     // Initial state where nothing is happening
    WAITING,  // Signal that the foreground thread is waiting
    RUNNING,  // Signal that the background thread is running
    DONE      // Signal that the background thread is done
}

private StateWatcher state = new StateWatcher(State.IDLE);

[...]

new Thread(() ->
{
    state.waitFor(WAITING); 
    state.signal(RUNNING);

    doThings();
    
    state.signal(DONE);
    
}).start();

state.signal(WAITING);
state.waitFor(DONE);

在这个例子中,你可能期望这段代码有一个竞赛条件。如果线程启动并在前台线程达到signal(WAITING)之前达到waitFor( WAITING),则没有问题。但如果前台线程发出信号说它在等待,并在后台线程启动前继续等待DONE,那该怎么办?在Java监控器(或条件)下,信号会被后台线程错过。然后,它将永远挂起,等待一个永远不会出现的等待信号。前台线程也会挂起,等待一个永远不会到来的DONE信号。这是一个典型的死锁场景。

StateWatcher通过使信号和等待成为有状态的操作,解决了这个问题。在我们的竞赛条件下,前台线程像以前一样调用信号(WAITING)。但这个信号并没有丢失。相反,StateWatcher在继续等待DONE之前,会记录它处于WAITING状态。如果后台线程完成了启动并调用waitFor(WAITING)StateWatcher保留的当前状态仍然是WAITING,调用将立即返回而不是等待。我们的死锁被消除了,而且只用了极少的代码。StateWatcher为允许这种情况发生而保留的状态通常被称为条件变量

但是StateWatcher到底是如何实现这个魔法的呢?

StateWatcher有一个可以被更新的状态值,以及一个(KivaKit),它用来保护这个状态。它还维护着一个等待者列表,每个等待者都有一个要等待的条件由锁创建)和一个它需要满足的谓词

当*waitFor(Predicate

)*方法被调用时(如果观察者还没有处于所需的*状态*),一个新的*Waiter*对象(见下文)被创建,并带有*Predicate*和一个从*Lock*创建的*Condition*。然后,*waitFor()*方法将*Waiter*添加到等待列表中,并*awaits()*未来条件的信号。

当*signal(State)*被调用时,当前状态被更新,每个等待者被处理。如果一个等待者的谓词被新的状态所满足,它的条件对象被发出信号,导致等待谓词满足的线程被唤醒。

最后,*waitFor(State)被简单地实现了,它有一个对equals()*的方法引用作为谓词:

waitFor(desiredState::equals)

下面是StateWatcher的简化版本。完整的StateWatcher类可以在KivaKit项目的kivakit-kernel中找到:

public class StateWatcher<State>
{
    /**
     * A thread that is waiting for its predicate to be satisfied
     */
    private class Waiter
    {
        /** The predicate that must be satisfied */
        Predicate<State> predicate;

        /** The condition to signal and wait on */
        Condition condition;
    }

    /** The re-entrant (KivaKit) lock */
    private Lock lock = new Lock();

    /** The clients waiting for a predicate to be satisfied */
    private List<Waiter> waiters = new ArrayList<>();

    /** The most recently reported state */
    private State current;
    
    public StateWatcher(State current)
    {
        this.current = current;
    }

    /**
     * Signals any waiters if the state they are waiting for has arrived
     */
    public void signal(final State state)
    {
        lock.whileLocked(() ->
        {
            // Update the current state,
            current = state;

            // go through the waiters
            for (var watcher : waiters)
            {
                // and if the reported value satisfies the watcher's predicate,
                if (watcher.predicate.test(state))
                {
                    // signal it to wake up.
                    watcher.condition.signal();
                }
            }
        });
    }

    /**
     * Waits for the given boolean predicate to be satisfied based on changes * to the observed state value
     */
    public WakeState waitFor(Predicate<State> predicate)
    {
        return lock.whileLocked(() ->
        {
            // If the predicate is already satisfied,
            if (predicate.test(current))
            {
                // we're done.
                return COMPLETED;
            }

            // otherwise, add ourselves as a waiter,
            var waiter = new Waiter();
            waiter.predicate = predicate;
            waiter.condition = lock.newCondition();
            waiters.add(waiter);

            try
            {
                // and go to sleep until our condition is satisfied.
                if (waiter.condition.await())
                {
                    return TIMED_OUT;
                }
                else
                {
                    return COMPLETED;
                }
            }
            catch (InterruptedException e)
            {
                return INTERRUPTED;
            }
        });
    }

    /**
     * Wait forever for the desired state
     */
    public WakeState waitFor(State desired)
    {
        return waitFor(desired::equals);
    }
}

代码

StateWatcher类可在KivaKitkivakit-kernel模块中找到:

<dependency>
    <groupId>com.telenav.kivakit</groupId>
    <artifactId>kivakit-kernel</artifactId>
    <version>${kivakit.version}</version>
</dependency>