jdk-AbstractQueuedSynchronizer(四)

本文深入剖析了AQS框架中的Condition队列机制,详细解释了如何通过ReentrantLock配合Condition实现线程间的同步协作。文章重点介绍了Condition的await和signal方法的工作原理,以及它们在生产者-消费者模式中的具体应用。

第四篇来看看什么呢?来看看写第一篇时就想看的Condition 队列。AQS中维护了两个队列,一个sys等待队列,这个队列是那些想要去获取锁的线程获取不到而进入的一个获取锁的队列,另一个就是Condition条件等待队列。

本篇结合是ReentrantLock 去分析这个Condition条件队列。

       关于ReentrantLock和Condition结合使用,主要调用的代码是awqit和signal两个方法。

public final void await() throws InterruptedException {
        if (Thread.interrupted())
            //线程被打断的话直接抛出异常
            throw new InterruptedException();
        //这边就是加入Condition队列
        Node node = addConditionWaiter();
        //state的获取
        //release掉当前节点,释放锁
        int savedState = fullyRelease(node);
        int interruptMode = 0;
        //检查是否在锁队列中,不在的话进入循环,进入循环就是将自己睡着了
        // 在的话直接跳过循环,在的话说明当前线程有资格去竞争锁了。
        while (!isOnSyncQueue(node)) {
            LockSupport.park(this);
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
        }
        //acquireQueued就是之前说的加入锁队列。
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        if (node.nextWaiter != null) // clean up if cancelled
            unlinkCancelledWaiters();
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
    }
private Node addConditionWaiter() {
        Node t = lastWaiter;
        // If lastWaiter is cancelled, clean out.
        if (t != null && t.waitStatus != Node.CONDITION) {
            //注释很清楚,清除状态是cancelled掉的节点。
            unlinkCancelledWaiters();
            t = lastWaiter;
        }
        //构造出一个当前线程的节点,标志状态state为condition
        Node node = new Node(Thread.currentThread(), Node.CONDITION);
        if (t == null)
            firstWaiter = node;
        else
            t.nextWaiter = node;
        lastWaiter = node;
        //将第一个节点连接到node节点处
        return node;
    }

获取状态时顺便release掉当前节点,release时会唤醒锁队列中下一个为Signal状态的节点,之前分析过。

final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }


final boolean isOnSyncQueue(Node node) {
        //如果节点状态是-2了,说明不在了
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        
        if (node.next != null) // If has successor, it must be on queue
            return true;
        /*
         * node.prev can be non-null, but not yet on queue because
         * the CAS to place it on queue can fail. So we have to
         * traverse from tail to make sure it actually made it.  It
         * will always be near the tail in calls to this method, and
         * unless the CAS failed (which is unlikely), it will be
         * there, so we hardly ever traverse much.
         */
        //最终从尾部节点一直找,确保不在锁队列中
        return findNodeFromTail(node);
    }




不在锁队列中的话进入循环之后就LockSupport.park(this); 掉,线程到此为止。表名自己需要等待条件。

Thread1现在加入了等待队列中,而锁的队列里唤醒了下一个线程Thread 2,正在执行。那么当天执行signal的时候呢,就是发出一个信号了。

现在来看signal操作。

 public final void signal() {
        if (!isHeldExclusively())
            //判断是否是当前线程再执行signal操作
            throw new IllegalMonitorStateException();
        //获取头节点。
        Node first = firstWaiter;
        if (first != null)
            doSignal(first);
    }

private void doSignal(Node first) {
        do {
            if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
            first.nextWaiter = null;
        } while (!transferForSignal(first) &&
                (first = firstWaiter) != null);
        //在本例中,首次循环即进行状态设置,返回true,循环一次就退出while,do的操作就是将当前Condition中的节点给剔除掉,移动到锁队列中
    }

    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        //本例中第一个节点的状态为-2,修改为0,跳过。
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return 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).
         */
        //此处就是将Condition队列中的节点加入到锁队列中,
        Node p = enq(node);
        int ws = p.waitStatus;
        //一般情况下,这个队列中前驱节点的状态都是signal的,所以这一步一般不执行,
        //执行的话也是直接进行直接唤醒操作。
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

那么至此,Thread1又进入了锁队列中,等待获取锁。

总结一下。

1、首先,线程1调用lock.lock()时,由于此时锁并没有被其它线程占用,因此线程1直接获得锁并不会进入AQS同步队列中进行等待。

2、在线程1执行期间,线程2调用lock.lock()时由于锁已经被线程1占用,因此,线程2进入AQS同步队列中进行等待。

3、在线程1中执行condition.await()方法后,线程1释放锁并进入条件队列Condition中等待signal信号的到来。

4、线程2,因为线程1释放锁的关系,会唤醒AQS队列中的线程2,所以线程2会获取到锁。

5、线程2调用signal方法,这个时候Condition的等待队列中只有线程1一个节点,于是它被取出来,并被加入到AQS的等待队列中。注意,这个时候,线程1 并没有被唤醒。只是加入到了AQS等待队列中去了

6、待线程2执行完成之后并调用lock.unlock()释放锁之后,会唤醒此时在AQS队列中的头结点.所以线程1开始争夺锁(由于此时只有线程1在AQS队列中,因此没人与其争夺),如果获得锁继续执行。

简单的生产者消费者代码

public class Depot {

    private ReentrantLock lock = new ReentrantLock();

    /*生产者增加*/
    private Condition add = lock.newCondition();

    /*消费者消费*/
    private Condition remove = lock.newCondition();

    /*null说明仓库中没有值*/
    private Double num = null;

    public void add(){
        lock.lock();
        try{
            while (num != null){
                //num不为null说明生产者已经生产了数据在其中,需要等待消费者消费之后再次唤醒生成数据
                add.await();
            }
            num = Math.random();
            System.out.println(Thread.currentThread()+" 生产出一个货物 "+ num);
            //唤醒消费者,来消费啦
            remove.signal();
        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }

    public void remove(){
        lock.lock();
        try{
            while (num == null){
                //num 为null说明生产者没有生产数据,需要消费者等待生产者生产数据
                remove.await();
            }

            System.out.println(Thread.currentThread()+" 消费了一个货物 "+ num);
            num = null;
            //唤醒生产着,该生产了
            add.signal();
        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }

}
public class TestProvideAndFee {

    public static void main(String[] args) {

        final Depot depot = new Depot();

        for(int i = 0; i < 10; i ++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    depot.add();
                }
            }).start();
        }
        for(int i = 0; i < 10; i ++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    depot.remove();
                }
            }).start();
        }
    }
}
有一个生产者生产出货物,那么就需要一个消费者去消费。
Thread[Thread-5,5,main] 生产出一个货物 0.4885486784752844
Thread[Thread-11,5,main] 消费了一个货物 0.4885486784752844
Thread[Thread-9,5,main] 生产出一个货物 0.34176842004713925
Thread[Thread-16,5,main] 消费了一个货物 0.34176842004713925
Thread[Thread-6,5,main] 生产出一个货物 0.5197749409203888
Thread[Thread-13,5,main] 消费了一个货物 0.5197749409203888
Thread[Thread-2,5,main] 生产出一个货物 0.2647941282185792
Thread[Thread-17,5,main] 消费了一个货物 0.2647941282185792
Thread[Thread-3,5,main] 生产出一个货物 0.6240931467512665
Thread[Thread-15,5,main] 消费了一个货物 0.6240931467512665
Thread[Thread-8,5,main] 生产出一个货物 0.7776132350280154
Thread[Thread-10,5,main] 消费了一个货物 0.7776132350280154
Thread[Thread-7,5,main] 生产出一个货物 0.8128245646683492
Thread[Thread-19,5,main] 消费了一个货物 0.8128245646683492
Thread[Thread-1,5,main] 生产出一个货物 0.9477304285260686
Thread[Thread-14,5,main] 消费了一个货物 0.9477304285260686
Thread[Thread-4,5,main] 生产出一个货物 0.9339639929998004
Thread[Thread-12,5,main] 消费了一个货物 0.9339639929998004
Thread[Thread-0,5,main] 生产出一个货物 0.7505226432601498
Thread[Thread-18,5,main] 消费了一个货物 0.7505226432601498


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值