热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

java并发等待条件的实现原理详解

这篇文章主要介绍了java并发等待条件的实现原理详解,还是比较不错的,这里分享给大家,供需要的朋友参考。

前言

前面介绍了排它锁,共享锁的实现机制,本篇继续学习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 cOntainer= new LinkedList();
  // 用于标识产品
  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();
        }
      }
    }
  }
}

一次执行结果如下:

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.

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

深入理解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都处于等待状态,入同步队列等待。<喎&#65533;"/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxwPjxpbWcgYWx0PQ=="这里写图片描述" src="/uploadfile/Collfiles/20160912/20160912092710536.png" title="\" />

注意,本例中我们使用的是公平策略模式下的排它锁,由于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的时候,就会把等待节点转移到同步队列中,继续竞争锁。原理其实并不复杂,有兴趣的朋友可以翻阅源码。

以上就是本文关于java并发等待条件的实现原理详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站:java并发学习之BlockingQueue实现生产者消费者详解、Java并发之嵌套管程锁死详解、Java系统的高并发解决方法详解等,有什么问题可以随时留言,小编会及时回复大家的。感谢朋友们对本站的支持!


推荐阅读
  • 自己用过的一些比较有用的css3新属性【HTML】
    web前端|html教程自己用过的一些比较用的css3新属性web前端-html教程css3刚推出不久,虽然大多数的css3属性在很多流行的浏览器中不支持,但我个人觉得还是要尽量开 ... [详细]
  • 本文将深入探讨如何在不依赖第三方库的情况下,使用 React 处理表单输入和验证。我们将介绍一种高效且灵活的方法,涵盖表单提交、输入验证及错误处理等关键功能。 ... [详细]
  • Kubernetes 持久化存储与数据卷详解
    本文深入探讨 Kubernetes 中持久化存储的使用场景、PV/PVC/StorageClass 的基本操作及其实现原理,旨在帮助读者理解如何高效管理容器化应用的数据持久化需求。 ... [详细]
  • 本文作者分享了在阿里巴巴获得实习offer的经历,包括五轮面试的详细内容和经验总结。其中四轮为技术面试,一轮为HR面试,涵盖了大量的Java技术和项目实践经验。 ... [详细]
  • 本文介绍了如何利用 Spring Boot 和 Groovy 构建一个灵活且可扩展的动态计算引擎,以满足钱包应用中类似余额宝功能的推广需求。我们将探讨不同的设计方案,并最终选择最适合的技术栈来实现这一目标。 ... [详细]
  • 本文详细介绍了C++中map容器的多种删除和交换操作,包括clear、erase、swap、extract和merge方法,并提供了完整的代码示例。 ... [详细]
  • Python 内存管理机制详解
    本文深入探讨了Python的内存管理机制,涵盖了垃圾回收、引用计数和内存池机制。通过具体示例和专业解释,帮助读者理解Python如何高效地管理和释放内存资源。 ... [详细]
  • 深入解析Java枚举及其高级特性
    本文详细介绍了Java枚举的概念、语法、使用规则和应用场景,并探讨了其在实际编程中的高级应用。所有相关内容已收录于GitHub仓库[JavaLearningmanual](https://github.com/Ziphtracks/JavaLearningmanual),欢迎Star并持续关注。 ... [详细]
  • 本文探讨如何配置 Nginx 以将传入请求反向代理到运行在本地绑定端口上的 Docker 容器,并解决常见的路径重定向问题。 ... [详细]
  • 12月16日JavaScript变量、函数、流程、循环等***线上九期班
    12月16日JavaScript变量、函数、流程、循环等***线上九期班 ... [详细]
  • 深入解析Serverless架构模式
    本文将详细介绍Serverless架构模式的核心概念、工作原理及其优势。通过对比传统架构,探讨Serverless如何简化应用开发与运维流程,并介绍当前主流的Serverless平台。 ... [详细]
  • 本月初,我们为大家推荐了一系列精选书单,助力大家提升技术水平。月底,我们将介绍几位行业大牛,帮助大家找到人生导师。InfoQ一直致力于为用户提供有价值的资源和支持。 ... [详细]
  • 本文详细介绍了如何使用Docker运行最简单的镜像,并创建第一个容器。通过具体的操作步骤和命令解释,帮助初学者快速上手Docker。完整课程请点击:Docker入门教程。 ... [详细]
  • 深入解析SpringMVC核心组件:DispatcherServlet的工作原理
    本文详细探讨了SpringMVC的核心组件——DispatcherServlet的运作机制,旨在帮助有一定Java和Spring基础的开发人员理解HTTP请求是如何被映射到Controller并执行的。文章将解答以下问题:1. HTTP请求如何映射到Controller;2. Controller是如何被执行的。 ... [详细]
  • 深入解析Spring启动过程
    本文详细介绍了Spring框架的启动流程,帮助开发者理解其内部机制。通过具体示例和代码片段,解释了Bean定义、工厂类、读取器以及条件评估等关键概念,使读者能够更全面地掌握Spring的初始化过程。 ... [详细]
author-avatar
缘zhi韵_297
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有