ReentrantLock之Condition

 

1.介绍

等待/通知不仅可以使用0bject的wait()/notify()、notifyAll()方法。还可用通过RenntrantLock下面

的Condition来实现。

2.使用

/**
 * @Author AF
 * @Description
 * @Date 2019/12/28 18:19
 */
public class ConditionMain {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        new Thread(new ConditionWait(lock,condition)).start();
        new Thread(new ConditionSignal(lock,condition)).start();
    }
}
    public class ConditionWait implements Runnable {
        //锁
        private Lock lock;
        //同步队列
        private Condition condition;
        public ConditionWait(Lock lock, Condition condition) {
            this.lock = lock;
            this.condition = condition;
        }
        @Override
        public void run() {
            try{
                System.out.println("wait的锁地址"+lock);
                lock.lock();
                System.out.println("wait 开始阻塞");
                //阻塞当前线程
                condition.await();
                System.out.println("wait阻塞结束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("wait释放锁");
                lock.unlock();
            }
        }
}
public class ConditionSignal implements Runnable {
    private Lock lock;
    private Condition condition;

    public ConditionSignal(Lock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }
    @Override
    public void run() {
        try {
            System.out.println("signal的锁地址"+lock);
            lock.lock();
            System.out.println("signal开始唤醒");
            condition.signal();
            System.out.println("signal唤醒结束");
        } catch (Exception e) {

        } finally {
            System.out.println("signal释放锁");
            lock.unlock();
        }
    }
}
//===============
wait的锁地址java.util.concurrent.locks.ReentrantLock@24824c[Unlocked]
wait 开始阻塞
signal的锁地址java.util.concurrent.locks.ReentrantLock@24824c[Unlocked]
signal开始唤醒
signal唤醒结束
signal释放锁
wait阻塞结束
wait释放锁

3.Condition方法介绍

//线程阻塞
void await():
//有期限的阻塞    
boolean await(long time, TimeUnit unit):
//有期限的阻塞    
long awaitNanos(long nanosTimeout)
//线程阻塞
void awaitUninterruptibly()
//线程阻塞直到某个时间
boolean awaitUntil(Date deadline)
//======释放================
//释放条件队列中的firstWaiter
void signal()
//释放条件队列中的所以
void signalAll();
//condition的阻塞,会让当前线程释放锁,并且进去条件队列中
//awaitUninterruptibly和await()的区别是,使用前者阻塞后,调用thread.interrupt()不会报错,后者会报错

4.流程分析

newCondition()实际上是new ConditionObject(),而ConditionObject是AQS的内部类。从其源码中可以看出ConditionObject 是包含头结点和尾节点的。

public class ConditionObject implements Condition, java.io.Serializable {
    private static final long serialVersionUID = 1173984872572414699L;
    //头节点
    private transient Node firstWaiter;
    //尾节点
    private transient Node lastWaiter;

结构如下

当调用await()方法时,就会创建一个当前线程的node添加进队列中。使用的也是AQS的Node类。

相对于CLH对垒,Condition的条件队列的结构就简单很多。

AQS 等待队列与 Condition 队列是两个相互独立的队列

  • #await() 就是在当前线程持有锁的基础上释放锁资源,并新建 Condition 节点加入到 Condition 的队列尾部,阻塞当前线程 
  • #signal() 就是将 Condition 的头节点移动到 AQS 等待节点尾部,让其等待再次获取锁。

以下是 AQS 队列和 Condition 队列的出入结点的示意图,可以通过这几张图看出线程结点在两个队列中的出入关系和条件。

I.初始化状态:AQS等待队列有 3 个Node,Condition 队列有 1 个Node(也有可能 1 个都没有)

II.节点1执行 Condition.await()

  1. 将 head 后移
  2. 释放节点 1 的锁并从 AQS 等待队列中移除
  3. 将节点 1 加入到 Condition 的等待队列中
  4. 更新 lastWaiter 为节点 1

III.节点 2 执行 Condition.signal() 操作

  1. 将 firstWaiter后移
  2. 将节点 4 移出 Condition 队列
  3. 将节点 4 加入到 AQS 的等待队列中去
  4. 更新 AQS 的等待队列的 tail

 

5.源码分析

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)
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

private Node addConditionWaiter() {
    //获取尾节点
    Node t = lastWaiter;
    //Node的节点状态如果不为CONDITION,则表示该节点不处于等待状态,需要清除节点
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    //创建当前线程节点,并且将尾结点指向当前节点
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}
//释放锁
final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            //当使用await()之前,未使用lock.lock()加锁,则会抛出异常
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}
//如果一个节点刚开始在条件队列上,现在在同步队列上获取锁则返回 true
final boolean isOnSyncQueue(Node node) {
  // 状态为 Condition,获取前驱节点为 null ,返回 false
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    // 后继节点不为 null,肯定在 CLH 同步队列中
    if (node.next != null)
        return true;

    return findNodeFromTail(node);
}
// 等待队列是一个单向链表,遍历链表将已经取消等待的节点清除出去
// 纯属链表操作,很好理解,看不懂多看几遍就可以了
private void unlinkCancelledWaiters() {
    Node t = firstWaiter;
    Node trail = null; // 用于中间不需要跳过时,记录上一个 Node 节点
    while (t != null) {
        Node next = t.nextWaiter;
        // 如果节点的状态不是 Node.CONDITION 的话,这个节点就是被取消的
        if (t.waitStatus != Node.CONDITION) {
            t.nextWaiter = null;
            if (trail == null)
                firstWaiter = next;
            else
                trail.nextWaiter = next;
            if (next == null)
                lastWaiter = trail;
        }
        else
            trail = t;
        t = next;
    }}

通知signal()

public final void signal() {
    //检车当前线程是否为拥有锁的线程,如果不是,则报错
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
    //唤醒firstWaiter
        doSignal(first);
}

protected final boolean isHeldExclusively() {
    return getExclusiveOwnerThread() == Thread.currentThread();
}
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        //移除头节点,将下一个节点作为头结点
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        //将原来的头结点移到CLH队列中 
        transferForSignal(first);
        first = next;
    } while (first != null);
}
//调节队列中节点移到同步队列中
final boolean transferForSignal(Node node) {
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值