【线程】ReentrantLock + Condition 源码剖析 (九)

我的原则:先会用再说,内部慢慢来


一、await 与 signal 实例

public class _12_01_TestCondition{
	public static void main(String[] args) throws Exception{
		LockDemo lockDemo = new LockDemo();
		new Thread(() -> {
			try {
				lockDemo.await();
			} catch (Exception e) {
				e.printStackTrace();
			}
		},"A").start();

		new Thread(() -> {
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			lockDemo.signal();

		},"B").start();
	}
	static class LockDemo{
		ReentrantLock lock = new ReentrantLock();
		Condition condition = lock.newCondition();

		public void await() throws Exception{
			System.out.println(Thread.currentThread().getName() + ",condition ready to lock ");
			lock.lock();
			System.out.println(Thread.currentThread().getName() + ",condition ready to await ");
			condition.await();
			System.out.println(Thread.currentThread().getName() + ",condition ready to unlock ");
			lock.unlock();
		}
		public void signal(){
			lock.lock();
			System.out.println(Thread.currentThread().getName() + ",condition ready to signal ");
			condition.signal();
			lock.unlock();
		}
	}
}
  • 输出:
A,condition ready to lock 
A,condition ready to await 
B,condition ready to signal 
A,condition ready to unlock 
  • 结论

A 线程首先条件 lock , B 线程去唤醒, A线程继续往下跑

二、await 与 signal 实例,小小改动带来的Bug思考

把 lock 和 unlock 注释掉。

public void signal(){
//			lock.lock();
			System.out.println(Thread.currentThread().getName() + ",condition ready to signal ");
			condition.signal();
//			lock.unlock();
		}

  • 输出:
A,condition ready to lock 
A,condition ready to await 
B,condition ready to signal 
Exception in thread "B" java.lang.IllegalMonitorStateException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
	at indi.sword.util.concurrent._12_01_TestCondition$LockDemo.signal(_12_01_TestCondition.java:52)
	at indi.sword.util.concurrent._12_01_TestCondition.lambda$main$1(_12_01_TestCondition.java:33)
	at java.lang.Thread.run(Thread.java:748)
  • 结论

抛出了 IllegalMonitorStateException 异常:
IllegalMonitorStateException 讲解:【线程】 Object.wait 内部原理(二)

为什么会这样呢?
原因就是: condition.await(); 方法,已经释放锁了,signal与await方法调用的前提是必须拥有锁,否则抛异常~

接下来看 condition.await(); 方法。

三、 lock.newCondition()

  1. await()
public interface Condition {
    void await() throws InterruptedException;
}

由此可见,Condition 是一个 interface,await 需要被实现。

  1. 接下来看下初始化方法 Condition condition = lock.newCondition() ;
	// java.util.concurrent.locks.ReentrantLock#newCondition
    public Condition newCondition() {
        return sync.newCondition();
    }
	// java.util.concurrent.locks.ReentrantLock.Sync#newCondition
	 final ConditionObject newCondition() {
       return new ConditionObject();
	}

注意上面的 newCondition() 方法是 final,所以Sync 的实现类 NonfailSync 与 FailSync 统一使用该方法。

四、condition.await() 源码剖析

  • 接下来重点看一下 ConditionObject 的 await 方法
  • 原则:如果多个线程位于lock方法阻塞,那么丢到了AQS的syn同步队列,如果多个线程位于condition中的await方法阻塞,那么丢到了 AQS.ConditionObject的 condition队列,然后等到条件满足,被唤醒,又挨个放回到AQS的syn同步队列,等待唤醒
4.1 注意该方法内部 fullyRelease 释放了lock
void ConditionObject#await(){ 
	// thread 是否被打断,
    if (Thread.interrupted())
        throw new InterruptedException();
    // 1. 加入到 ConditionWaiter 里面去(新来的Node插入到Condition队列尾巴后面去)    
    Node node = addConditionWaiter();
    // 2. 释放掉当前Node中的thread所占用的lock
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 3. 如果没有在 Sync同步队列里面,那么就线程挂起,阻塞,等待唤醒
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        // 已经被唤醒了,看下线程是否被打断,打断的话,就break
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // 4. 被唤醒后,将节点从 Condition 队列移动到同步Sync队列,内部的 trylock方法将会获得成功,跑完下面的代码
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
    	// 如果node有下一个waiter,那么取消掉那些 cancel状态的 
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
  1. addConditionWaiter 方法
public final void AbstractQueuedSynchronizer.ConditionObject#addConditionWaiter{
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
    	// 移除掉waitStatus不是 CONDITION 的Node
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    // 创建一个 CONDITION 状态的 Node
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

1.1 unlinkCancelledWaiters 方法

// 移除掉waitStatus不是 CONDITION 的Node
private void AbstractQueuedSynchronizer.ConditionObject#unlinkCancelledWaiters() {
    Node t = firstWaiter;
    Node trail = null;
    while (t != null) {
        Node next = t.nextWaiter;
        if (t.waitStatus != Node.CONDITION) {
            t.nextWaiter = null;
            // 只有第一次循环,且第一个的waitStatus不是Node.CONDITION的时候,才会进去下面的if
            if (trail == null)
                firstWaiter = next;
            else
            	// 链表跳过中间的 t
                trail.nextWaiter = next;
            if (next == null)
                lastWaiter = trail;
        }
        else
            trail = t;
        t = next;
    }
}
  1. fullyRelease 方法 (释放掉当前Node中的thread所占用的lock)
final int AbstractQueuedSynchronizer#fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        // 释放锁 
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
        	// 若当前没有持有锁,那么抛 IllegalMonitorStateException 异常
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

2.1 release 方法

public final boolean AbstractQueuedSynchronizer#release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

后续 release方法的深入,直接看上一篇文章有讲解这个,这里不累赘
【线程】ReentrantLock 源码剖析 (八)

  1. isOnSyncQueue 方法
// 判断是否在同步Sync队列中,是的话,就阻塞
final boolean AbstractQueuedSynchronizer#isOnSyncQueue(Node node) {
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    if (node.next != null) // If has successor, it must be on queue
        return true;
    return findNodeFromTail(node);
}

3.1 findNodeFromTail 方法

private boolean AbstractQueuedSynchronizer#findNodeFromTail(Node node) {
    Node t = tail;
    for (;;) {
        if (t == node)
            return true;
        if (t == null)
            return false;
        t = t.prev;
    }
}
  1. acquireQueued 方法
// node 入同步队列
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);
        }
    }

五、 单 Await 场景剖析

  1. await 之前,lock.lock 获取锁

在这里插入图片描述

  1. addConditionWaiter :加入到 ConditionWaiter 里面去
    在这里插入图片描述

  2. fullyRelease :释放掉当前Node中的thread所占用的lock
    在这里插入图片描述

  3. LockSupport.park(this); 进入阻塞,等待唤醒

六、单 condition.signal() 源码剖析

  • 看下 signal 方法
public final void AbstractQueuedSynchronizer.ConditionObject#signal() {
	// 若当前thread 没有持有锁,那么抛出Exception
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
    	// 唤醒第一个 waiter
        doSignal(first);
}
  • 看下 doSignal 方法
private void AbstractQueuedSynchronizer.ConditionObject#doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

在这里插入图片描述

  • 看下 transferForSignal 方法

 // transferForSignal把当前节点从condition队列迁移到同步队列中,并设置waitStatus为SIGNAL
final boolean AbstractQueuedSynchronizer#transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    // 如果不能更改waitStatus,则该Node已被取消
    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).
     */
    // 前驱节点,enq 是把节点加到 Sync的尾巴端
    Node p = enq(node);
    int ws = p.waitStatus;

    // TODO: debug 的时候,不知道 p.waitStatus 为什么会突然从 0 变成 -1. 纳闷。
     // 正常不是在这里唤醒的,是在lock.unclock的时候,release的时候唤醒的
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
    	// 如果是 cancel 状态或者是其他状态,才会进入这个 unpark 方法,提前叫醒。
        LockSupport.unpark(node.thread);
    return true;
}

方法本意是将一个节点从Condition队列转换为AbstractQueuedSynchronizer队列,
总结一下方法的实现:

  1. 尝试将Node的waitStatus从CONDITION置为0,这一步失败直接返回false
  2. 当前节点进入调用enq方法进入AbstractQueuedSynchronizer队列
  3. 当前节点通过CAS机制将waitStatus置为SIGNAL
    最后上面的步骤全部成功,返回true,返回true唤醒等待节点成功。从唤醒的代码我们可以得出一个重要结论:某个await()的节点被唤醒之后并不意味着它后面的代码会立即执行,它会被加入到AbstractQueuedSynchronizer队列的尾部,只有前面等待的节点获取锁全部完毕才能轮到它。

在这里插入图片描述

  • 某个await()的节点被唤醒之后并不意味着它后面的代码会立即执行,它会被加入到AbstractQueuedSynchronizer队列的尾部,只有前面等待的节点获取锁全部完毕才能轮到它。也就是 unlock 的时候,会唤醒同步队列的Node内部的 thread。

=========== 至此,单Condition,单Await,单Signal流程结束 ===========

七、多个 condition.await() 实例demo

public class _12_02_TestCondition {
	public static void main(String[] args) throws Exception{
		LockDemo lockDemo = new LockDemo();
		for (int i = 0; i < 5; i++) {
			new Thread(() -> {
				try {
					lockDemo.await();
				} catch (Exception e) {
					e.printStackTrace();
				}
			},"A" + i).start();
		}
		// 确保上面代码先执行
		Thread.sleep(1000);
		Scanner sc = new Scanner(System.in);
		System.out.println("点击任意键唤醒线程 ...");
		sc.nextLine();

		for (int i = 0; i < 5; i++) {
			new Thread(() -> {
				lockDemo.signal();

			},"B" + i).start();
		}
	}
	static class LockDemo{
		ReentrantLock lock = new ReentrantLock();
		Condition condition = lock.newCondition();
		public void await() throws Exception{
			System.out.println(Thread.currentThread().getName() + ",condition ready to lock ");
			lock.lock();
			System.out.println(Thread.currentThread().getName() + ",condition ready to await ");
			condition.await();
			System.out.println(Thread.currentThread().getName() + ",condition ready to unlock ");
			lock.unlock();
		}
		public void signal(){
			System.out.println(Thread.currentThread().getName() + ",condition ready to lock ");
			lock.lock();
			System.out.println(Thread.currentThread().getName() + ",condition ready to signal ");
			condition.signal();
			System.out.println(Thread.currentThread().getName() + ",condition ready to unlock ");
			lock.unlock();
		}
	}
}

输出:

A0,condition ready to lock 
A0,condition ready to await 
A1,condition ready to lock 
A1,condition ready to await 
A2,condition ready to lock 
A2,condition ready to await 
A3,condition ready to lock 
A3,condition ready to await 
A4,condition ready to lock 
A4,condition ready to await 
点击任意键唤醒线程 ...



B0,condition ready to lock 
B0,condition ready to signal 
B0,condition ready to unlock 
A0,condition ready to unlock 
B1,condition ready to lock 
B1,condition ready to signal 
B1,condition ready to unlock 
B3,condition ready to lock 
B3,condition ready to signal 
B3,condition ready to unlock 
B2,condition ready to lock 
B2,condition ready to signal 
B2,condition ready to unlock 
B4,condition ready to lock 
B4,condition ready to signal 
B4,condition ready to unlock 
A1,condition ready to unlock 
A2,condition ready to unlock 
A3,condition ready to unlock 
A4,condition ready to unlock 

七、多个 condition.await() 场景分析

在这里插入图片描述

  1. await源码剖析
  2. addConditionWaiter源码剖析
  3. addConditionWaiter场景剖析
  • 假设5个线程全部 lock.lock ,都暂时未进入 await 方法。那么,全部enq 进入同步sync队列,具体看这个
    【线程】ReentrantLock 源码剖析 (八)

  • 5个线程全部进入await方法,最终全部线程 park 阻塞,丢到 Condition 队列,等待再次丢到同步队列Sync之后被唤醒Signal

一个等待Condition 队列 + 一个同步Sync队列:
在这里插入图片描述
最终只剩下等待队列:
在这里插入图片描述

八、多个 condition.signal() 场景分析

  1. signal源码剖析
  2. signal场景剖析
  • signal 的过程,就是把等待队列里面的Node添加到Sync队列的过程:

在这里插入图片描述

  • 彩色图加深理解
    在这里插入图片描述

  • 附加一张总体架构图,同时存在等待队列和同步Sync队列:
    在这里插入图片描述

  • 跑过 LockSupport.park(this); 之后,已经被Signal,那么接着往下走,全部丢到 Sync同步队列里面。lock.unlock 的时候,将释放 Sync 队列中的节点。
    在这里插入图片描述
    ======== 全部讲解结束 ========

九、番外篇

下一章节:【线程】ReentrantLock 内部公平锁与非公平锁实现 (十)
上一章节:【线程】ReentrantLock 源码剖析 (八)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值