java并发等待条件的实现原理(Condition)

前言

前面介绍了排它锁,共享锁的实现机制,本篇继续学习AQS中的另外一个内容-Condition。想必学过java的都知道Object.wait和Object.notify,同时也应该知晓这两个方法的使用离不开synchronized关键字。synchronized是jvm级别提供的同步原语,它的实现机制隐藏在jvm实现中。作为Lock系列功能中的Condition,就是用来实现类似 Object.wait和Object.notify 对应功能的。


使用场景

为了更好的理解Lock和Condition的使用场景,下面我们先来实现这样一个功能:有多个生产者,多个消费者,一个产品容器,我们假设容器最多可以放3个产品,如果满了,生产者需要等待产品被消费,如果没有产品了,消费者需要等待。我们的目标是一共生产10个产品,最终消费10个产品,如何在多线程环境下完成这一挑战呢?下面是我简单实现的一个demo,仅供参考。 

package com.lock.condition.test;

import java.util.LinkedList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class LockConditionTest {
    // 生产 和 消费 的最大总数
    public static int totalCount = 10;
    // 已经生产的产品数
    public static volatile int hasProduceCount = 0;
    // 已经消费的产品数
    public static volatile int hasConsumeCount = 0;
    // 容器最大容量
    public static int containerSize = 3;
    // 使用公平策略的可重入锁,便于观察演示结果
    public static ReentrantLock lock = new ReentrantLock(true);
    public static Condition notEmpty = lock.newCondition();
    public static Condition notFull = lock.newCondition();
    // 容器
    public static LinkedList<Integer> container = new LinkedList<Integer>();
    // 用于标识产品
    public static AtomicInteger idGenerator = new AtomicInteger();

    public static void main(String[] args) {
        Thread p1 = new Thread(new Producer(), "p-1");
        Thread p2 = new Thread(new Producer(), "p-2");
        Thread p3 = new Thread(new Producer(), "p-3");

        Thread c1 = new Thread(new Consumer(), "c-1");
        Thread c2 = new Thread(new Consumer(), "c-2");
        Thread c3 = new Thread(new Consumer(), "c-3");

        c1.start();
        c2.start();
        c3.start();
        p1.start();
        p2.start();
        p3.start();
        try{
            c1.join();
            c2.join();
            c3.join();
            p1.join();
            p2.join();
            p3.join();
        }catch(Exception e){

        }
        System.out.println(" done. ");
    }
    static class Producer implements Runnable{
        @Override
        public void run() {
            while(true){
                lock.lock();
                try{
                    // 容器满了,需要等待非满条件
                    while(container.size() >= containerSize){
                        notFull.await();
                    }

                    // 到这里表明容器未满,但需要再次判断是否已经完成了任务
                    if(hasProduceCount >= totalCount){
                        System.out.println(Thread.currentThread().getName()+" producer exit");
                        return ;
                    }

                    int product = idGenerator.incrementAndGet();
                    // 把生产出来的产品放入容器
                    container.addLast(product);
                    System.out.println(Thread.currentThread().getName() + " product " + product);
                    hasProduceCount++;

                    // 通知消费线程可以去消费了
                    notEmpty.signal();
                } catch (InterruptedException e) {
                }finally{
                    lock.unlock();
                }
            }
        }
    }
    static class Consumer implements Runnable{
        @Override
        public void run() {
            while(true){
                lock.lock();
                try{
                    if(hasConsumeCount >= totalCount){
                        System.out.println(Thread.currentThread().getName()+" consumer exit");
                        return ;
                    }

                    // 一直等待有产品了,再继续往下消费
                    while(container.isEmpty()){
                        notEmpty.await(2, TimeUnit.SECONDS);
                        if(hasConsumeCount >= totalCount){
                            System.out.println(Thread.currentThread().getName()+" consumer exit");
                            return ;
                        }
                    }

                    Integer product = container.removeFirst();
                    System.out.println(Thread.currentThread().getName() + " consume " + product);
                    hasConsumeCount++;

                    // 通知生产线程可以继续生产产品了
                    notFull.signal();
                } catch (InterruptedException e) {
                }finally{
                    lock.unlock();
                }
            }
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
一次执行的结果如下:
p-1 product 1
p-3 product 2
p-2 product 3
c-3 consume 1
c-2 consume 2
c-1 consume 3
p-1 product 4
p-3 product 5
p-2 product 6
c-3 consume 4
c-2 consume 5
c-1 consume 6
p-1 product 7
p-3 product 8
p-2 product 9
c-3 consume 7
c-2 consume 8
c-1 consume 9
p-1 product 10
p-3 producer exit
p-2 producer exit
c-3 consume 10
c-2 consumer exit
c-1 consumer exit
p-1 producer exit
c-3 consumer exit
 done. 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

从结果可以发现已经达到我们的目的了。 


深入理解Condition的实现原理

上面的示例只是为了展示 Lock结合Condition可以实现的一种经典场景,在有了感性的认识之后,我们将一步一步来观察Lock和Condition是如何协作完成这一任务的,这也是本篇的核心内容。

为了更好的理解和演示这一个过程,我们使用到的锁是使用公平策略模式的,我们会使用上面例子运作的流程。我们会使用到3个生产线程,3个消费线程,分别表示 p1、p2、p3和c1、c2、c3。

Condition的内部实现是使用节点链来实现的,每个条件实例对应一个节点链,我们有notEmpty 和 notFull 两个条件实例,所以会有两个等待节点链。

一切准备就绪 ,开始我们的探索之旅。

1、线程c3执行,然后发现没有产品可以消费,执行 notEmpty.await,进入等待队列中等候。

这里写图片描述 

2、线程c2和线程c1执行,然后发现没有产品可以消费,执行 notEmpty.await,进入等待队列中等候。

这里写图片描述 

3、 线程 p1 启动,得到了锁,p1开始生产产品,这时候p3抢在p2之前,执行了lock操作,结果p2和p3都处于等待状态,入同步队列等待。

这里写图片描述

注意,本例中我们使用的是公平策略模式下的排它锁,由于p3抢先执行取锁操作,所以虽然p2和p3都被阻塞了,但是p3会优先被唤醒 。 

4、这会,p1生产完毕,通知 not empty等待队列,可以唤醒一个等待线程节点了,然后释放了锁,释放锁会导致p3被唤醒,然后p1进入下一个循环,进入同步队列。

这里写图片描述

事情开始变得有趣了,p1执行一次生产后,执行了 notEmpty.signal,其效果就是把 not empty等待列表中的头节点,即c3节点移到同步等待列队中,重新参与抢占锁。 

5、p3生产完了产品后,继续notEmpty.signal,同时释放锁,释放锁后会唤醒p2线程,然后p3在下一轮尝试获取锁的时候,再次入队。

这里写图片描述


6、接着,p2继续生产,生产后执行 notEmpty.signal,同时释放锁,释放锁后唤醒c3线程,然后p2在下一轮尝试取锁的时候,入列。

这里写图片描述


7、c3进行消费,你可以看到,现在 not empty等待列队中已经没有等待节点了,由于我们使用的是公平策略排它锁,这就会导致同步队列中的节点一个接着一个执行,而目前同步队列中的节点排列为一生产,一消费,这不难可以知道,接下来代码已经不会进入 wait条件了,所以一个一个轮流执行就是,比如c3,执行完了,继续notFull.signal(); 然后释放锁,入队,这里要明白,notFull.signal();这句代码其实没有作用了,因为 not full等待队列中没有任何等待线程节点。 c3执行后,状态如下图所示:

这里写图片描述


8、后面的事情我想大家都可以想得出来是怎样一步一步交替执行的了。


总结

本篇基于一个实例来演示结合Lock和Condition如何实现生产-消费模式,而且只讨论一种可能执行的流程,是想更简单的表述AQS底层是如何实现的。基于上面这个演示过程,针对其它的执行流程,其原来也是一样的。Condition内部使用一个节点链来保存所有 wait状态的线程,当对应条件被signal的时候,就会把等待节点转移到同步队列中,继续竞争锁。原理其实并不复杂,有兴趣的朋友可以翻阅源码。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值