Java Condition接口使用Demo和原理分析

1.Demo

1.1 使用场景

先上一个使用场景:
多个线程之间按照顺序调用,实现ABC三个线程启动,要求如下:

  • AA打印5次,BB打印10次,CC打印15次
  • 紧接着,AA打印5次,BB打印10次,CC打印15次
  • …来10轮

1.2 代码实现

//共享资源类
class ShareResource {
    //A 1  B 2  C 3
    private int number = 1;
    private Lock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    public void print5() {
        lock.lock();
        try {
            //1.判断
            while (number != 1) {
                c1.await();
            }
            //2.干活
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            //通知
            number = 2;
            c2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print10() {
        lock.lock();
        try {
            //1.判断
            while (number != 2) {
                c2.await();
            }
            //2.干活
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            //通知
            number = 3;
            c3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print15() {
        lock.lock();
        try {
            //1.判断
            while (number != 3) {
                c3.await();
            }
            //2.干活
            for (int i = 1; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            //通知
            number = 1;
            c1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class SyncAndReentrantLockDemo {
    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareResource.print5();
            }
        }, "AA").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareResource.print10();
            }
        }, "BB").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                shareResource.print15();
            }
        }, "CC").start();
    }
}

1.3 代码说明

场景关键是要求三个线程是按照ABC的顺序执行,循环10轮,不能乱。
demo中总共有3个线程,A B C,分别调用print5(),print10(),print15()三个方法;
在三个方法中,分别持有3个condition对象,都是先调用wait()方法,等其他线程的signal;然后干完活在使用signal()通知别人抢锁干活。
注意每个方法中都判断了number的数值,number初始值是1,所以AA线程在调用print5()方法时,不会进入wait()方法;相反,其他两个线程要么在阻塞的等待lock锁,要么会进入wait()等待通知抢锁。

2.Condtion原理分析

Condtion是个接口,所有方法的实现都在实现类中实现,好在实现类并不多,只有如下两个:
在这里插入图片描述
其实就是AQS类中的ConditionObject内部类(AQS:AbstractQueuedSynchronizer,此类是比较重要的基础方法类,感兴趣可以单独了解,本文只分析Condtion相关的方法,不单独分析AQS中的其他方法);
ConditionObject类中有连个属性:firstWaiter和lastWaiter,说明ConditionObject也是维护了一个condition队列,注意和AQS队列不是同一个队列。 下文中使用condition队列和AQS队列进行区分。
在这里插入图片描述
下面看wait方法源码。

2.1 await()方法

public final void await() throws InterruptedException {
  if (Thread.interrupted())
        throw new InterruptedException();
    //初始化一个等待节点,加入等待队列尾部
    Node node = addConditionWaiter();
    //释放AQS同步队列中的节点,就是释放锁,因为调用await肯定是已经获取到锁的
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //isOnSyncQueue(node):检查node节点是否在AQS的队列中
    //如果不在,说明还没有被唤醒,没有资格竞争锁,进入沉睡状态
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //如果已经在AQS同步队列中,acquireQueued方法则进行抢锁
    //acquireQueued中如果抢不到,还会再次进入睡眠状态,等待下一次通知抢锁
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

步骤总结:
1.初始化一个等待节点,加入condition队列尾部,具体看下面addConditionWaiter()源码注释;
2.释放AQS同步队列中的节点,因为之前它肯定是已经拿到锁的;现在都到等待状态了,需要释放之前拿到的锁
3. while (!isOnSyncQueue(node)) 会判断当前线程是否又出现在了AQS队列中,如果没有出现,则继续睡眠等待,如果出现在等待队列中(别的线程调用了signal方法),则进行抢锁;如果抢不到,则继续睡眠。

//初始化一个等待节点,加入等待队列尾部,并返回此节点
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    //判断当前尾部节点的状态,如果不是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;
}

2.2 signal()方法

继续看通知方法:

public final void signal() {
//能走到这个方法,说明应该是拿到独占锁的,不然就抛异常
   if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    //拿到condition等待队列中的头节点,不是空的话,说明有人在等,通知此人
    if (first != null)
        doSignal(first);
}
//通知队列中的第一个人
private void doSignal(Node first) {
    do {
        //让等待队列中的第二个节点变成头节点
        //如果第二个节点是null,说明没有尾节点了,赋值null
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

通知的关键方法是transferForSignal(first),继续看此方法:

final boolean transferForSignal(Node node) {
     //将此node的wait状态设置为由Node.CONDITION设置为0,
     //如果失败,说明node的状态不是 Node.CONDITION,可能已经被取消了
    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;
}

步骤总结:通知其实比较简单,分为两步,
1.首先获取到condition队列中的第一个节点,通知此节点
2.通知方法具体动作就是将此节点迁移到AQS队列中去抢锁

以上就是Condition接口的两个核心方法的原理,

注意这里有个比较绕的逻辑关系,lock和aqs和condition这3个对象,

我们new了3个condition对象,那么肯定有3个condition队列,但是,condition是AQS的内部非静态类,是否也存在3个AQS队列?实际通过效果来看,这里只有1个AQS实例对象,否则3个conditon对象将没有任何联系,也就无法进行联动相互通知了。

为什么只有1个AQS对象?我们明明new了3次啊?这里涉及到一个知识点:
看看ReentrantLock的newCondition方法:

 public Condition newCondition() {
     return sync.newCondition();
 }
 //sync对象的newCondition方法
  final ConditionObject newCondition() {
      return new ConditionObject();
  }

最终使用的是Sync的newCondition,而Sync继承了AQS,因此相当于是AQS对象的newCondition,此时new出来的3个condition对象,相当于AQS对象的3个内部属性 ,而sync对象又是lock对象的内部属性,所以3个condition对象也就是lock对象的3个内部属性。也就是说,condition对象在进行await()和signal()时,实际上操作的是lock对象内部的sync属性对象中的aqs属性。

最后画一个图:
在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一个简单的Java Condition接口使用示例: 假设有一个共享的缓冲区,其中生产者线程可以将数据项添加到缓冲区中,而消费者线程可以从中移除数据项。缓冲区有一个最大容量,当缓冲区已满时,生产者线程需要等待直到缓冲区中有空间可以添加数据。同样,当缓冲区为空时,消费者线程需要等待直到有数据项可供消费。 可以使用JavaCondition接口来实现此类同步。以下是一个示例代码: ```java import java.util.LinkedList; import java.util.Queue; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Buffer { private Queue<Integer> buffer; private int maxSize; private Lock lock; private Condition notFull; private Condition notEmpty; public Buffer(int maxSize) { this.maxSize = maxSize; buffer = new LinkedList<>(); lock = new ReentrantLock(); notFull = lock.newCondition(); notEmpty = lock.newCondition(); } public void put(int num) throws InterruptedException { lock.lock(); try { while (buffer.size() == maxSize) { notFull.await(); } buffer.offer(num); System.out.println("Produced " + num); notEmpty.signal(); } finally { lock.unlock(); } } public int take() throws InterruptedException { lock.lock(); try { while (buffer.size() == 0) { notEmpty.await(); } int num = buffer.poll(); System.out.println("Consumed " + num); notFull.signal(); return num; } finally { lock.unlock(); } } } ``` 在这个示例中,我们使用一个有限的缓冲区实现了一个简单的生产者-消费者模型。我们定义了一个缓冲区队列和一个最大容量。我们还使用Java的Lock和Condition接口来确保线程之间的同步。 在构造函数中,我们初始化了缓冲区,锁和条件变量。我们使用ReentrantLock作为锁,使用newCondition()方法创建了两个条件变量:notFull和notEmpty。 在put()方法中,我们首先获取锁,然后使用while循环检查缓冲区是否已满。如果是,则调用notFull.await()方法将当前线程阻塞,直到缓冲区中有足够的空间可以添加新元素。如果缓冲区未满,则将新元素添加到缓冲区中,并通过notEmpty.signal()方法通知其他可能正在等待的消费者线程可以消费数据。最后,我们释放锁。 在take()方法中,我们首先获取锁,然后使用while循环检查缓冲区是否为空。如果是,则调用notEmpty.await()方法将当前线程阻塞,直到缓冲区中有可消费的元素。如果缓冲区不为空,则从缓冲区中取出一个元素,并通过notFull.signal()方法
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值