java对共享变量的操作管理使用了MESA管程模型。下图是Java基于AQS实现的MESA管程模型:
让客户满意是我们工作的目标,不断超越客户的期望值来自于我们对这个行业的热爱。我们立志把好的技术通过有效、简单的方式提供给客户,将通过不懈努力成为客户在信息化领域值得信任、有价值的长期合作伙伴,公司提供的服务项目有:域名与空间、网络空间、营销软件、网站建设、惠水网站维护、网站推广。
上图中有三个知识点:
本文主要讲解管程模型中条件变量等待队列。
首先我们看一下官方给出的示例代码:
- public class BoundedBuffer {
- final Lock lock = new ReentrantLock();
- final Condition notFull = lock.newCondition();
- final Condition notEmpty = lock.newCondition();
- final Object[] items = new Object[100];
- int putptr, takeptr, count;
- public void put(Object x) throws InterruptedException {
- lock.lock();
- try {
- while (count == items.length)
- notFull.await();
- items[putptr] = x;
- if (++putptr == items.length) putptr = 0;
- ++count;
- notEmpty.signal();
- } finally {
- lock.unlock();
- }
- }
- public Object take() throws InterruptedException {
- lock.lock();
- try {
- while (count == 0)
- notEmpty.await();
- Object x = items[takeptr];
- if (++takeptr == items.length) takeptr = 0;
- --count;
- notFull.signal();
- return x;
- } finally {
- lock.unlock();
- }
- }
- }
这个代码定义了两个条件变量,notFull和notEmpty,说明如下:
Java AQS的条件变量等待队列是基于接口Condition和ConditionObject来实现的,URM类图如下:
Condition接口主要定义了下面3个方法:
条件等待队列跟入口等待队列有两个不同:
await方法的流程如下图:
3.1 进入条件等待队列
入队方法对应方法addConditionWaiter,这里有三种情况:
可以看到,这种情况会从队列第一个元素开始检查waitStatus不是-2的元素,并从队列中移除。
3.2 释放锁
AQS的并发锁是基于state变量实现的,线程进入条件等待队列后,要释放锁,即state会变为0,释放操作会唤醒入口等待队列中的线程。对应方法fullyRelease,返回值是释放锁减掉的state值savedState。
3.3 阻塞等待
释放锁后,线程阻塞,自旋等待被唤醒。
3.4 唤醒之后
唤醒之后,当前线程主要有四个动作:
waitStatus等于0表示中间状态,当前节点后面的节点已经唤醒,但是当前节点线程还没有执行完成。
3.5 一个细节
上面提到了interruptMode,这个属性有三个值:
3.6 扩展
AQS还提供了其他几个await方法,如下:
唤醒条件等待队列中的元素,首先判断当前线程是否持有独占锁,如果没有,抛出异常。
唤醒条件队列中的元素,会从第一个元素也就是firstWaiter开始,根据firstWaiter的waitStatus是不是-2,分两种情况。
4.1 waitStatus==-2
条件队列第一个节点进入入口等待队列,等待获取锁,如下图:
这里有两个注意点:
如果重置waitStatus状态失败,则unpark节点firstWaiter。
4.2 waitStatus!=-2
如果firstWaiter的waitStatus不等于-2,则查找firstWaiter的nextWaiter,直到找到一个waitStatus等于-2的节点,然后将这个节点加入入口等待队列队尾,如下图:
4.3 waitStatus修改
上面的两种情况无论哪种,进入入口等待队列之前都要用CAS的方式把waitStatus改为0。
理解了signal的逻辑,signalAll的逻辑就非常容易理解了。首先判断当前线程是否持有独占锁,如果没有,抛出异常。
将条件等待队列中的所有节点依次加入入口等待队列。如下图:
6.1 示例代码
java并发包下有很多类使用到了AQS中的Condition,如下图:
这里我们以CyclicBarrier为例来讲解。CyclicBarrier是让一组线程相互等待共同达到一个屏障点。从Cyclic可以看出Barrier可以循环利用,也就是当线程释放之后可以继续使用。
看下面这段示例代码:
- public static void main(String[] args) {
- CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> {
- System.out.println("栅栏中的线程执行完成");
- });
- ExecutorService executorService = Executors.newFixedThreadPool(2);
- executorService.submit(() -> {
- try {
- System.out.println("线程1:" + Thread.currentThread().getName());
- cyclicBarrier.await();
- } catch (Exception e) {
- e.printStackTrace();
- }
- });
- executorService.submit(() -> {
- try {
- System.out.println("线程2:" + Thread.currentThread().getName());
- cyclicBarrier.await();
- } catch (Exception e) {
- e.printStackTrace();
- }
- });
- executorService.shutdown();
- }
执行结果:
- 线程1:pool-1-thread-1
- 线程2:pool-1-thread-2
- 栅栏中的线程执行完成
6.2 原理讲解
CyclicBarrier初始化的时候,会指定线程的数量count,每个线程执行完逻辑后,调用CyclicBarrier的await方法,这个方法首先将count减1,然后调用Condition的await,让当前线程进入条件等待队列。当最后一个线程将count减1后,count数量等于0,这时就会调用Condition的signalAll方法唤醒所有线程。
java的管程模型使用了MESA模型,基于AQS实现的MESA模型中,使用双向队列实现了入口等待队列,使用变量state实现了并发锁,使用Condition实现了条件等待队列。
在AQS的实现中,使用同步队列这个术语来表示双向队列,本文中使用入口等待队列来描述是为了更好的配合管程模型来讲解。
AQS的Condition中,使用await方法将当前线程放入条件等待队列阻塞等待,使用notify来唤醒条件等待队列中的线程,被唤醒之后,线程并不能立刻执行,而是进入入口等待队列等待获取锁。
当前文章:10张图详解管程内部,进去看看
链接分享:http://www.shufengxianlan.com/qtweb/news22/224572.html
网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联