Condition案例使用及其源码解析

目录

前言

condition案例-结合ReentrantLock

代码如下

结果展示

结果分析

condition和ReentrantLock源码分析

lock方法

await方法

addConditionWaiter方法

fullyRelease方法

release方法

isOnSyncQueue方法

await方法阻塞线程1完毕,线程2抢锁

线程2通过signal方法主动释放线程1并cas抢占锁

signal方法

doSignal方法

transferForSignal方法

enq方法

线程1被unpark后做了什么操作?

acquireQueued方法

总结:


  • 在这里插入图片描述

前言

必须先看懂ReentrantLock才能看这个Condition,因为condition是配套ReentrantLock来使用的.如下.
AQS是什么?基于ReentrantLock解密!
下面会基于ReentrantLock的加锁和释放锁来结合Condition的阻塞释放锁以及唤醒加锁进行使用讲解以及源码解析
老规矩,还是要一步一图,不然是看不懂的.

condition案例-结合ReentrantLock

  • 代码如下,首先需要明确:condition使用的await()方法相当于wait()方法,signal()方法相当于notify()方法

代码如下

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class TestReentrantLockAndConditionDemo {
    static  ReentrantLock reentrantLock=new ReentrantLock();
    static Condition condition = reentrantLock.newCondition();
    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    reentrantLock.lock();
                    System.out.println("线程1加锁成功");
                    System.out.println("线程1开始阻塞");
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                reentrantLock.unlock();
                System.out.println("线程1释放锁成功");
            }
        });
        thread1.start();
        Thread.sleep(1000);
        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程2开始加锁");
                reentrantLock.lock();
                System.out.println("线程2加锁成功");
                condition.signal();
                System.out.println("线程2释放线程1");
                reentrantLock.unlock();
                System.out.println("线程2释放锁完成");
            }
        });
        thread2.start();
    }
}

 

结果展示

线程1加锁成功
线程1开始阻塞
线程2开始加锁
线程2加锁成功
线程2释放线程1
线程2释放锁完成
线程1释放锁成功

结果分析

  • 只要线程1被await,那么就会被放入condition等待队列里面,然后释放锁,等待被唤醒
  • 此时线程2过来进行加锁,然后最后释放线程1调用signal,线程1会从condition队列维护的阻塞队列node最终会转移到AQS中的阻塞队列里面.继续等待线程2释放锁
  • 线程2释放锁,之后,线程1会重新从阻塞队列里面释放出来进行加锁.

基于这个案例我们可以通过一步一图的形式去查看ReentrantLock和condition的源码

condition和ReentrantLock源码分析

  • 首先ReentrantLock的加锁lock方法,先看上面案例的线程1加锁lock

lock方法

public void lock() {
    sync.lock();
}
final void lock() {
    if (compareAndSetState(0, 1)) //假设这里cas成功,state++.
        //设置aqs加锁线程为线程1
        setExclusiveOwnerThread(Thread.currentThread());
    else
        //这里是多线程抢锁才会触发.不分析了,有疑问去
        //看ReentrantLock或者ReentrantReadWriteLock内部的分析
        acquire(1);
}

如下图.
image.png

await方法

  • 然后调用condition的await方法,释放线程1抢到的锁,并且加入到condition内部维护的一个等待队列里面
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //获取或创建condition等待队列的当前线程创建的node节点.
    Node node = addConditionWaiter();
    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);
}

面涉及到了好多方法.逐个分析

addConditionWaiter方法

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    //第一次调用await方法会新创建一个队列,专门用于存储被await的线程,
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;//firstWaiter第一个waiter指针指向node
    else
        t.nextWaiter = node;
    lastWaiter = node; //最后一个waiter指针指向node
    return node;//最后返回.
}

此时流程如下:创建一个线程1node—专属于condition阻塞队列. waitStatus为CONDITION类型.也就是-2

image.png

  • 执行完addConditionWaiter方法之后,会接着走await剩下的方法
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //获取或创建condition等待队列的当前线程创建的node节点.
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);//将线程1node传进去.不了解继续看下面的fullyRelease方法解析
    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);
}

fullyRelease方法

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();//获取当前state的值.假设此时为1,只有线程1加了一次锁
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}


 

  • 通过release(1)释放线程1加的锁.

release方法

public final boolean release(int arg) {//很熟悉了....在reentrantLock解析的时候释放锁的代码一模一样.
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    int nextc = getState() - releases;  //state-1 释放锁
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        //清除线程1的加锁线程为null
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}

此时流程图如下:
image.png

  • fullyRelease方法会最终返回1.
  • 执行完fullyRelease方法之后,会接着走await剩下的方法
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //获取或创建condition等待队列的当前线程创建的node节点.
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);//将线程1node传进去.不了解继续看下面的fullyRelease方法解析
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {//isOnSyncQueue最终会返回false,进入while循环,阻塞住线程1
        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);
}

isOnSyncQueue方法

    final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)//满足条件,此时直接返回false
            return false;
        if (node.next != null) // If has successor, it must be on queue
            return true;
        return findNodeFromTail(node);
    }

  • 此时await最终 执行LockSupport.park(this);方法阻塞住线程1

image.png

await方法阻塞线程1完毕,线程2抢锁

  • 当线程1被await方法阻塞住之后,线程2就可以通过lock方法进行加锁,加锁代码同上

流程图如下:线程2加速cas成功,将state加1,所占用线程更新为线程2自己
image.png

线程2通过signal方法主动释放线程1并cas抢占锁

signal方法

  • signal方法同notify方法,都是唤醒一个线程的.我们看代码
 public final void signal() {
     if (!isHeldExclusively())
         throw new IllegalMonitorStateException();
     Node first = firstWaiter; //获取condition队列的第一个节点,也就是线程1node
     if (first != null)
         doSignal(first);//进行唤醒
 }

通过doSignal方法唤醒线程1

doSignal方法

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null) 
            lastWaiter = null;
        first.nextWaiter = null;//将firstWaiter和lastWaiter都置为null
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

image.png

transferForSignal方法

 final boolean transferForSignal(Node node) {
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) //这里线程1进行cas将线程1状态改为0
            return false;//直接返回.如果cas成功.会执行下面.
     	//执行enq方法从condition队列转移到AQD阻塞队列里面
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

image.png

enq方法

  private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

 

  • 还是一步一图.首先将t指针指向tail 因为tail指针本身是null,所以在aqs内部创建一个虚拟阻塞节点.然后将tail指向head.如下图

image.png

  • 然后继续循环,t指针指向tail也就是nodeHead,线程1node的prev指向t,也就是指向nodeHead ,然后设置node为tail尾节点.nodeHead的next指针指向node节点,然后返回nodeHead节点,如下图

image.png

  • 我们接着看transferforSignal方法
 final boolean transferForSignal(Node node) {
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0)) //这里线程1进行cas将线程1状态改为0
            return false;//直接返回.如果cas成功.会执行下面.
     	//执行enq方法从condition队列转移到AQD阻塞队列里面,如上图
        Node p = enq(node);
        int ws = p.waitStatus;//更改nodehead虚拟节点的状态为-1
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);//释放线程1.
        return true;
    }

image.png

线程1被unpark后做了什么操作?

  • 其实跟普通唤醒阻塞队列里面的线程类似.重回await方法
 public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            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);
        }

acquireQueued方法

  • 这里的唤醒操作跟那个reentrantLock的唤醒一模一样.
 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

首先获取线程1node的前驱节点也就是nodeHead ,第一个if假设线程1通过tryAcquire获取到了锁

tryAcquire内部会调用下面代码cas重试加锁.
final boolean nonfairTryAcquire(int acquires) {
     final Thread current = Thread.currentThread();
     int c = getState();
     if (c == 0) {
         if (compareAndSetState(0, acquires)) {//state线程++
             setExclusiveOwnerThread(current);//将线程1设置为加锁线程
             return true;
         }
     }
     else if (current == getExclusiveOwnerThread()) {
         int nextc = c + acquires;
         if (nextc < 0) // overflow
             throw new Error("Maximum lock count exceeded");
         setState(nextc);
         return true;
     }
     return false;
 }

如下图:将线程1node的内部线程清空
image.png
继续执行acquireQueued方法剩余逻辑

setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;

private void setHead(Node node) {
        head = node;  //头结点指向node
        node.thread = null;//清空线程1node内部tread
        node.prev = null;
    }

 

  • 将线程1node变为新的nodehead节点.原nodeHead会被垃圾回收掉.

image.png

总结:

  1. await方法会释放掉通过reentrantlock加的lock锁,同时让当前线程阻塞到 condition队列里面阻塞等待.直至被其他线程唤醒
  2. signal方法会被其他线程调用去释放调用被阻塞在condition队列的阻塞节点.转移到aqs对应的node阻塞链表里面,然后继续阻塞.
  3. 直到其他线程unlock掉自己占用的锁.就会去unpark中断 aqs内部的阻塞队列的线程阻塞.然后将其从阻塞队列中移除.

转载:西木东林  - Condition案例使用及其源码解析

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值