JUC-Condition使用以及Condition原理分析

1. 线程通信

ps:要想理解Condition原理,需要先了解AQS,不了解AQS的可以看先之前的文章->aqs源码解析

在Synchronized加锁状态时,是使用wait/notify/notifyAll进行线程间的通信。那么在使用ReentrantLock加锁时,是如何实现线程间通信问题的呢?在JUC中既然提供了Lock,也提供了用作其线程间通信的方式,再次引入了Condition。

2. 使用场景

我们先来看一下Condition是如何使用的:
唤醒线程:

public class ConditionNotify implements Runnable {

    private Lock lock;

    private Condition condition;

    public ConditionNotify(Lock lock, Condition condition) {
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        try {
            lock.lock();

            System.out.println("begin - notify");
            condition.signal();
            System.out.println("end - notify");

        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }

}

阻塞线程:

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 {
            lock.lock();

            System.out.println("begin - wait");
            condition.await();
            System.out.println("end - wait");

        }catch (InterruptedException e){

        }finally {
            lock.unlock();
        }
    }
}

运行:

public class Main {

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        // 保证两个线程获取的是同一把锁 和 同一个COndition

        new Thread(new ConditionWait(lock, condition)).start();
        new Thread(new ConditionNotify(lock, condition)).start();
    }
}

运行结果:
运行结果

  1. ConditionWait线程运行,争抢到锁后,进入阻塞状态,释放锁
  2. ConditionNotify线程运行,争抢到锁,同时唤醒ConditionWait线程
  3. ConditionWait被唤醒,争抢到锁,继续运行

3. 原理分析

假如两个线程A、B同时争抢同一把锁,线程A先获取到锁的时候,线程B出入等待队列中,队列状态如下:
在这里插入图片描述

3.1 Condition

		final ConditionObject newCondition() {
            return new ConditionObject();
        }

Condition对象使用执行lock.newCondition()获取的,会创建一个ConditionObject对象返回。我们先来看一下ConditionObject的结构:

		private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;

它有两个成员对象(很重要),分别表示头结点和尾节点构成一个单向队列,当调用await方法时,会将线程加入的这个队列中,下文会讲。

3.2 await方法

线程A获取到锁处于运行状态,当调用Condition的await方法时,具体做了哪些事情:

		public final void await() throws InterruptedException {
		    if (Thread.interrupted())
		        throw new InterruptedException();
		    // 将线程加入到Condition队列中
		    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);
		}

await方法调用addConditionWaiter方法,将线程加入到Condition队列中去:

		private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            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;
        }

然后调用在await方法中调用fullyRelease释放锁,后续走AQS逻辑会从AQS队列中唤醒等待线程B,这个时候线程B获取到锁,队列状态转换如下:
在这里插入图片描述

3.3 signal方法

当线程B运行时,会调用signal方法,signal方法逻辑如下:

		public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
            	// 将Condition队列中的线程移除,加入到
                doSignal(first);
        }

		final boolean transferForSignal(Node node) {
	        /*
	         * If cannot change waitStatus, the node has been cancelled.
	         */
	        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).
	         */
	         // 加入到AQS队列
	        Node p = enq(node);
	        int ws = p.waitStatus;
	        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
	        	// 如果没有线程占用锁,就唤醒该线程
	            LockSupport.unpark(node.thread);
	        return true;
	    }

signal方法执行后,将Condition队列中的线程加入到AQS队列,(如果这个时候线程B运行完释放了锁就会唤醒线程B,)队列状态如下:
在这里插入图片描述
直到两个线程都运行完成,两个队列都会为空。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值