Java多线程基础(二):线程的等待和通知

442 阅读3分钟

1、Wait 和 Notify

位于Object类中有两套方法wait和notify,这两套方法是如何实现线程间的协作,即线程的等待和通知呢? 先看一下方法描述:

方法名称 描述
wait() 调用该方法的线程进入WAITING状态,只有等待另外线程的通知或被中断才会返回,需要注意调用wait()方法后,会释放对象的锁
wait(long) 超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回
wait(long, int) 对于超时时间更细粒度的控制,可以达到纳秒
notify() 通知一个在对象上等待的线程,使其从wait()方法返回,而返回的前提是该线程获取到了对象的锁
notifyAll() 通知所有等待在该对象上的线程

线程靠调用某个对象的wait方法进行阻塞,线程进入wait set中,通过调动该对象的notify方法从wait set中出来继续执行。

2、为什么必须在同步代码块中使用?

wait必须在synchronized同步代码块中

  1. 表面上,直接调用wait会抛IllegalMonitorStateException异常,该异常顾名思义,没有拿到monitor(监视器)。该异常类注释如下:

    Thrown to indicate that a thread has attempted to wait on an object's monitor or to notify other threads waiting on an object's monitor without owning the specified monitor.

    简单翻译,调用wait或者notify,却没有拥有object's monitor(对象监视器,即进入synchronized同步代码)。

  2. 逻辑上,两个线程调用wait和notify存在竞态条件。

    当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。

    本来应该线程a先执行wait,线程b在执行notify。结果线程b先执行了,a再执行wait,那a这不永远就在等待了。所以用synchronized的同步代码块,来保证线程a、b对竞态资源的访问,只允许一个线程先取得,也就是同步,保证了线程的执行顺序。

    怎么同步代码块就能控制顺序?因为写代码的时候可以让哪个线程先进入同步代码块,即谁先拿到对象的monitor。

  3. 实现上,这要看JDK关于线程、wait、notify这些native代码了。monitor是用ObjectMonitor实现的,线程有个ObjectMonitor列表,来记录该线程获取对象的monitor情况。

    ObjectMonitor中定义了 _WaitSet 、_EntryList 、_owner

    • _WaitSet :处于wait状态的线程,会被加入到wait set。
    • _EntryList:处于等待锁block状态(等待获取对象monitor)的线程,会被加入到entry set。
    • _owner:获取到对象monitor的线程。

    在网上找到一个图:

    该图描绘出了三者关系和流程:

    1. 线程等待进入同步代码块(synchronized),进入Entry Set,处于Blocking状态。
    2. 线程获取到object's monitor,进入同步代码块。
    3. 线程调用wait方法,释放object's monitor,进入Wait Set,处于Waiting状态。
    4. object的notifyAll方法被调用,线程出Wait Set
    5. The Owner指向拥有object's monitor的线程,处于Runnale状态。

3、通用实践

通用的实践粘贴一段来自Disruptor框架中的代码。

/**
 * Blocking strategy that uses a lock and condition variable for {@link EventProcessor}s waiting on a barrier.
 * <p>
 * This strategy can be used when throughput and low-latency are not as important as CPU resource.
 */
public final class BlockingWaitStrategy implements WaitStrategy
{
    // (1) 定义一个object
    private final Object mutex = new Object();

    @Override
    public long waitFor(long sequence, Sequence cursorSequence, Sequence dependentSequence, SequenceBarrier barrier)
        throws AlertException, InterruptedException
    {
        long availableSequence;
        if (cursorSequence.get() < sequence)
        {
            // (1) wait使用
            synchronized (mutex)
            {
                while (cursorSequence.get() < sequence)
                {
                    barrier.checkAlert();
                    mutex.wait();
                }
            }
        }

        while ((availableSequence = dependentSequence.get()) < sequence)
        {
            barrier.checkAlert();
            ThreadHints.onSpinWait();
        }

        return availableSequence;
    }

    @Override
    public void signalAllWhenBlocking()
    {
        // (2) notify使用
        synchronized (mutex)
        {
            mutex.notifyAll();
        }
    }

    @Override
    public String toString()
    {
        return "BlockingWaitStrategy{" +
            "mutex=" + mutex +
            '}';
    }
}

(1) 定义一个对象,好使用该对象的monitor、wait、notify。

(2) 的注释中先获取对象的monitor,然后循环等待某种资源,资源不满足时,不断的调用wait释放monitor给notify机会。直到资源满足后,跳出循环。

(3) 的注释中已经获取到对象的monitor,调用notifyAll。