Condition的await和signal原理详解(Condition下的生产消费模型)

Condition的await和signal与Object的wait与notify区别:

任何一个Java对象都天然继承于Object类,在线程间实现通信会用到Object的几个方法,如wait(),wait(long timeout),wait(long timeout,int nanos)与notify(),notifyAll()这几个方法实现等待通知机制,同样的,在Java Lock体系有同样的方法实现等待通知机制。从整体上看Object的wait与notify是与对象监视器(synchronized同步代码块或者同步方法中)配合完成线程间的等待通知机制,而Condition的await和signal与Lock配合完成等待通知机制,前者是JVM底层级别(不可以看源码),后者是Java语言级别,具有更高的可控制性和扩展性(可以看源码)。
两者在功能特性上还有如下不同:
1.Condition支持不响应中断,而Object不支持,也就是Object只要有中断就要响应。
2.Condition支持多个等待队列(new多个Condition对象),而Object只有一个等待队列,但两者都只要一个同步队列;
3.Condition支持截止时间设置,而Object是超时时间设置,支持截止时间设置,不用计算需要等多久。

Condition :

public interface Lock {
   Condition newCondition();
   }
public interface Condition {
	//当前线程进入等待状态,直到被中断或唤醒
	 void await() throws InterruptedException;

	//不响应中断(即使有中断,也不会抛异常),直到被唤醒
	 void awaitUninterruptibly();
	 
	 //当前线程进入等待状态直到被通知,中断或者超时;
	 long awaitNanos(long nanosTimeout) throws InterruptedException;

	//同Object.wait(long timeout),多了自定义时间单位
	//直到超时,中断,被唤醒
	 boolean await(long time, TimeUnit unit) throws InterruptedException;
	 //支持设置截止时间,直到到截止时间、中断、被唤醒
	 boolean awaitUntil(Date deadline) throws InterruptedException;

	//唤醒一个等待在Condition(等待队列)上的线程,将该线程由等待队列转移到同步队列
	 void signal();
    //将所有等待在condition上的线程全部转移到同步队列中
	 void signalAll();

Condition实现原理分析:


等待队列

创建一个Condition对象是通过lock.newCondition( ),而这个方法实际上会new出一个ConditionObject对象,该类是AQS的一个内部类(lock的实现原理依赖AQS)。

 public class ConditionObject implements Condition, java.io.Serializable {
 		/** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;
}

在ConditionObject 通过持有等待队列的头尾指针来管理等待队列。这个Node复用了AQS的Node类,也就是等待队列和同步队列的结点一个Node类。
Node类中有这样一个属性:

//后继节点
Node nextWaiter;

nextWaiter是等待队列中标识下一个结点,也就是说Node结点的prev和next等待队列没有用到。
等待队列是一个单向队列,而同步队列是一个双向队列。

package CODE.多线程;


import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Await {
    public static void main(String[] args) {
        Lock lock=new ReentrantLock();
        Condition condition=lock.newCondition();
        for(int i=0;i<10;i++)
        {
            Thread thread=new Thread(()->
            {
                lock.lock();
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            });
            thread.start();
        }
    }
}

在这里插入图片描述
Debug模式下可以看见:
1.调用condition.wait方法后线程依次尾插到等待队列中,对上例来说,顺序依次是Thread-0,Thread-1,Thread-2…Thread-9;
2.等待你队列是一个单向队列,只有nextWaiter。
由于condition是new出来的,也就是说当多次调用lock.newCondition()可以创建多个condition对象,也就是一个lock可以有多个等待队列。
用Object类wait在Object对象监视器上只有一个同步队列和一个等待队列,而在并发包中的Lock中有一个同步队列多个等待队列。

在这里插入图片描述

Condition的应用:实现有界队列
有界队列是一种特殊的队列,当队列为空时,队列的获取(删除)操作将会阻塞获取(删除)线程,直到队列中有新增结点;当队列已满时,队列的插入操作将会阻塞添加线程,直到队列出现空位。
代码如下:

package CODE.多线程;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//Conditon实现有界队列
class BoundQueue<T>
{
    private Object[] items;
    private int counts=0;  //intems中元素个数
    private Lock lock=new ReentrantLock();
    private Condition fullCondition=lock.newCondition();
    private Condition emptyCondition=lock.newCondition();
    public BoundQueue(int size)
    {
        items=new Object[size];
    }

    //向数组里添加元素,如果数组满,进入等待状态
    public void add(T t,int addIndex) throws InterruptedException {
        try
        {
            lock.lock();
            //数组已满,添加线程需要进入等待状态
            while(counts==items.length)
            {
                System.out.println("数组已满,需要等待");
                fullCondition.await();
            }
            System.out.println(Thread.currentThread().getName()+"在添加元素");
            items[addIndex]=t;
            counts++;
            //元素添加完毕,需要唤醒清空队列
            emptyCondition.signal();
        }finally {
            lock.unlock();
        }

    }
    //删除元素方法,如果当前数组为空,移除线程进入等待状态直到数组不为空
    public T remove(int removeIndex) throws InterruptedException {
        try
        {
            lock.lock();
            while(counts==0)
            {
                System.out.println("数组已空,删除等待");
               emptyCondition.await();
            }
            Object x=items[removeIndex];
            System.out.println(Thread.currentThread().getName()+"在删除元素");
            counts--;
            //唤醒添加线程
            fullCondition.signal();
            return (T)x; //从大类型到小类型需要强转
        }finally {
            lock.unlock();
        }
    }

}
class MyThread implements Runnable
{
    private BoundQueue boundQueue;
    private int flag;
    public MyThread(int flag,BoundQueue boundQueue)
    {
        this.boundQueue=boundQueue;
        this.flag=flag;
    }
    public void run()
    {
        if(flag==1)
        {
            try {
                boundQueue.add("asb",0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        else
        {
            try {
                boundQueue.remove(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class Bound {
    public static void main(String[] args) {
        BoundQueue<String> boundQueue=new BoundQueue<>(2);
        MyThread addthread=new MyThread(1,boundQueue);
        MyThread removethread=new MyThread(0,boundQueue);
        new Thread(removethread,"删除线程1").start();
        new Thread(addthread,"添加线程1").start();
        new Thread(addthread,"添加线程2").start();
    }

}

在这里插入图片描述

await实现原理


源码如下:

 public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
                //将当前线程包装成Node结点尾插到等待队列
            Node node = addConditionWaiter();
            //由于尾插到等待队列,调用await会释放锁,那么当前线程释放所占用的lock,释放后唤醒同步队列中下一个结点
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
            //当前结点不再同步队列时,阻塞该线程,进入等待状态
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //退出循环的条件:被唤醒进入同步队列和被中断
            //被唤醒后进入同步队列自旋竞争同步状态
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
                //处理中断
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

调用condition.await( )方法后:
1.当前线程释放lock,进入等待队列,并且唤醒同步队列中下一个结点;
2.当前线程被signal/signalAll后从等待队列移至同步队列,直到获取lock才从await方法返回或者在等待时被中断会做中断处理。

接下来思考几个问题:
1.当前线程如何添加到等待队列中?
2.当前线程释放锁的过程?
3.如何从await方法退出?

1.调用addConditionWaiter将当前线程添加到等待队列中,
源码如下:

 private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            //将当前线程包装成Node结点
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            //等待队列为空,firstWaiter指向当前线程结点
            if (t == null)
                firstWaiter = node;
            else
            	//等待队列不为空,尾插到等待队列
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

addConditionWaiter():
1.如果等待队列最后一个结点是取消状态,将这个线程移除;
2.将当前线程包装成Node结点,如果等待队列为空(firstWaiter为null),将firstWaiter指向新包装的Node结点,否则,将当前线程尾插到等待队列,更新
lastWaiter(尾节点)。
总之:通过尾插的方式将当前线程封装的Node结点插入到等待队列中,同时,可以发现等待队列是一个没有头结点的队列。
等待队列是一个无头结点单向的链式队列;
同步队列是一个有头尾结点的双向链式队列。

2.释放当前锁,fullyRelease()
当当前线程结点尾插到同步队列后,会释放lock锁,唤醒同步队列中下一个结点,源码如下:

 /**
     * Invokes release with current state value; returns saved state.
     * Cancels node and throws exception on failure.
     * @param node the condition node for this wait
     * @return previous sync state
     */
    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            //调用AQS的释放同步状态方法release(),并唤醒同步队列后继结点
            if (release(savedState)) {
                //成功释放锁状态
                failed = false;
                return savedState;
            } else {
            //释放同步状态失败抛异常
                throw new IllegalMonitorStateException();
            }
        } finally {
        //释放同步状态后将当前结点设置为取消状态
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

fullyRelease():
1.调用AQ的模板方法release方法释放同步状态并且唤醒在同步队列中头结点的后继结点。
2.该线程释放lock成功则正常返回,否则抛异常。

3.如何从await方法退出

while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }

注意while循环退出条件:该结点在同步队列和该线程在等待过程被中断。
当第一次调用condition.await(),会进入while循环(这时已经释放锁,进入等待队列),然后通过LockSupport.park(this)使当前线程阻塞进入等待状态。
如果有线程调用codition的signal或者signalAll方法该线程会进入到同步队列,while循环为false,退出循环;
如果当前线程在等待的过程被中断会break。

总之:当前线程被中断或者调用condition.signal/condition.signalAll方法使当前线程移到同步队列,会退出while循环。退出while循环后,如果在同步队列中调用acquireQueued(node, savedState)自旋获取同步状态,直至线程获取锁退出awaqit方法;退出while循环后,如果被中断则处理中断异常。

在这里插入图片描述

当调用condition.await方法的线程必须是已经获取了lock,也就当前线程是同步队列的头结点。调用await后,会使当前线程封装成Node尾插到等待队列中。

signal/signalAll实现原理

当调用condition.signal()会使等待队列等待时间的线程结点也就是头结点移动到同步队列;当调用condition.signalAll()会使等待队列中所有结点移动到同步队列中。

signal源码为:

/**
         * Moves the longest-waiting thread, if one exists, from the
         * wait queue for this condition to the wait queue for the
         * owning lock.
         *
         * @throws IllegalMonitorStateException if {@link #isHeldExclusively}
         *         returns {@code false}
         */
        public final void signal() {
        //当前线程是否持有lock
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //获取等待队列中第一个结点,第一个结点不为空,doSignal()
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

doSignal()

/**
         * Removes and transfers nodes until hit non-cancelled one or
         * null. Split out from signal in part to encourage compilers
         * to inline the case of no waiters.
         * @param first (non-null) the first node on condition queue
         */
        private void doSignal(Node first) {
            do {
            //先将等待队列第一个结点指向下一个结点,如果为空,证明等待队列只有当前线程,那么将lastWaiter指向null
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } 
            //transferForSignal方法对该线程节点做真正的处理
            while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

transferForSignal()

 /**
     * Transfers a node from a condition queue onto sync queue.
     * Returns true if successful.
     * @param node the node
     * @return true if successfully transferred (else the node was
     * cancelled before signal)
     */
    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
         //首先将结点状态设置为0
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;  //失败直接返回false

        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
         //将结点使用enq尾插到同步队列中
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

Condition.signal():
1.调用该方法的线程必须获取到lock锁;
2.调用该方法使等待队列的头结点即等待时间最长的线程结点尾插到同步队列,移到同步队列才有机会使得等待队列被唤醒,即从await方法中的LockSupport.park(this)方法中返回,从而有机会使得调用await方法的线程成功。
在这里插入图片描述
signalAll( )

/**
         * Moves all threads from the wait queue for this condition to
         * the wait queue for the owning lock.
         *
         * @throws IllegalMonitorStateException if {@link #isHeldExclusively}
         *         returns {@code false}
         */
        public final void signalAll() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignalAll(first);
        }
 /**
         * Removes and transfers all nodes.
         * @param first (non-null) the first node on condition queue
         */
        private void doSignalAll(Node first) {
            lastWaiter = firstWaiter = null;
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first);
                first = next;
            } while (first != null);  //唤醒了所有结点
        }

signalAll()将等待队列中每一个结点都移到同步队列中,即“通知”当前调用condition.await()方法的每一个线程。

Condition机制实现生产–消费者模型

package CODE.多线程;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

////Condition机制实现生产---消费者模型
class Goods1
{
    private String goodsname;
    private int maxCount;  //产品// 最大数量
    private int counts;  //当前产品数量
    private Lock lock=new ReentrantLock();
    private Condition proCondition=lock.newCondition();  //生产者等待队列
    private Condition consumerCondition=lock.newCondition(); //消费者等待队列
    public Goods1(int maxCount)
    {
        this.maxCount=maxCount;
    }

    //生产产品
    public void set(String goodsname)
    {
        lock.lock();
        try
        {
            while(counts==maxCount)
            {
                System.out.println("产品已达最大数量,稍后生产");
                try {
                    proCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.goodsname=goodsname;
            counts++;
            System.out.println(Thread.currentThread().getName()+"生产"+goodsname+":"+counts);
            consumerCondition.signalAll(); //唤醒消费者
        }finally {
            lock.unlock();
        }
    }

    public void get()
    {
        lock.lock();
        try
        {
            while(counts==0)
            {
                System.out.println("没有产品,稍后来消费");
                try {
                    consumerCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            counts--;
            System.out.println(Thread.currentThread().getName()+"消费"+goodsname+"还剩"+counts);
            proCondition.signalAll();
        }finally {
            lock.unlock();
        }
    }
}
class proConThread implements Runnable
{
    private   Goods1 goods1;
    private int flag;  //用flag标识是生产者还是消费者线程

    public proConThread(Goods1 goods1, int flag) {
        this.goods1 = goods1;
        this.flag = flag;
    }
    public void run()
    {
        if(flag==1)
        {
            while(true)
            {
                goods1.set("奥利奥饼干");
            }
        }
        else
            while(true)
            {
                goods1.get();
            }
    }
}
public class ConPro {
    public static void main(String[] args) {
        Goods1 goods1 = new Goods1(100);
        proConThread proThread = new proConThread(goods1, 1);
        proConThread conThread = new proConThread(goods1, 0);
        List<Thread> list = new ArrayList<>();  //数组存的是生产消费线程,因为生产消费线程需要同时启动

        for (int i = 0; i < 5; i++) {
            Thread pThread = new Thread(proThread, "生产者" + i);
            list.add(pThread);
        }
        for (int i = 0; i < 3; i++) {
            Thread cThread = new Thread(conThread, "消费者" + i);
            list.add(cThread);
        }
        for(Thread thread:list)
        {
            thread.start();
        }
    }
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/sophia__yu/article/details/84502578
今日推荐