前言

由于Java高级并发编程主要是针对多线程并发访问公共资源控制来展开的,而现在服务器大多都是多核处理,所以在执行控制中,单一的同步锁无法满足需要,为此Java 1.5开始引入了三个重要的概念Semaphores,CountDownLatch和CyclicBarrier其实这三个实现类都是基于以整型原子操作状态值操作的执行控制类。

严格意义上讲,它们都是对原子操作int类型表示状态来控制锁操作的封装。我们知道,在原子类型操作中,我们避免了使用锁机制而采用CAS机制达到了操作的原子性不可中断,从而乐观的实现了共享资源的访问。

但是在我们现实开发中,有很多场景需要对多个操作进行分组控制,也就是说允许指定数量的线程进入临界区操作。

这种情况常见于各类池化原理实现上。比如我们有一个提供多线程处理的服务,每次允许5个客户端接入,超过5个就需要排队等待。

如此我们就需要一个组控制机制来实现。为此我们可以封装一个由原子int类型变量表示的计数器,由这个计数器来统计每次进入临界区的线程数量,通过原子读取和增减来决定等待队列线程的获取进入许可,同时当线程离开临界区后,将计数器恢复。

依次来实现对线程的组控制。

信号量

我们可以想象信号量就是一个全局的看护者,它维护着一组许可证,每次有线程想获取一个许可证时它会根据情况阻塞等待许可证是可用状态,如果可用就颁发给线程。每次线程释放许可证,同时也意味着放开对获取许可证的占有,许可证变为可申请。

而实际上,其实并没有真正的许可对象被引用。信号量仅仅是一个计数变量而已。

信号量通常用于限制访问一些物理或逻辑资源的线程的数量。

比如,我们在设计线程池时,就可以定义一个最大线程数的信号量来控制它。

大体思路是,我们设计一个可用的对象实例集合,其长度是线程池的最大数。

然后设计一个其对应状态数组,保存这些对象可用状态。

然后使用同步操作来检查每个对象的对应的状态,如果发现可用对象,就将对象取出使用。

同时维护另外一个同步线程操作检查这些对象状态,如果发现对象可用就标记它为可用状态。

然后我们可以在这个线程池的对外操作时定一个信号量来限制获取线程对象,在线程对象被占满时手动释放线程。

在获得一个项目之前,每个线程必须从信号量那里获得一个许可证,从而保证其操作资源可用。

当线程已经完成了操作离开临界区后,许可就会回到池中和信号量计数相应的递减,允许后续线程获取许可进入。

信号量获取许可调用acquire()方法,可以指定获取数量,默认获取一个许可。

注意,这个获取过程是没有同步锁的,因为一旦加了同步锁将阻止许可被返还个池子中。

信号量封装了限制对池的访问所需的同步,与维护池本身一致性所需的任何同步分开。

初始化为一个信号量的信号量(使用该信号量时最多只能有一个许可证)可以用作互斥锁。

这种信号量常被称为二进制信号量,因为它只有两个状态存在:许可可用或者没有许可可用,

我们使用二进制信号量时,二进制信号量具有这样的属性:

“锁”可以由所有者以外的线程释放!

由于信号量跟普通锁不同,它没有所有权的概念,这一点不同于 java.util.concurrent.locks.Lock 的实现。

这在某些特定情形里很有用,比如死锁恢复

信号量有个特别之处就是它允许后来先得,也就是说,调用acquire的线程可以在已经等待的线程之前分配一个许可证。

在逻辑上,实际上就是新线程将自己放在等待线程队列的最前面。如此就是高优先级掠夺政策,易造成低优先级线程被饿死。

下面是一个简单的示例来演示信号量的定义和使用:

图 2-1

图2-2

所以,在构建信号量时,我们可以将公平性参数设置为true,信号量保证调用acquire()方法的线程被选中,以获得处理这些方法调用的顺序(FIFO,先进先出)。

注意,不定时的#tryAcquire()该获取方法不尊重公平设置,但会接受任何许可。

通常,用于控制资源访问的信号量应该初始化为公平的,来确保没有线程被饿死。

当将信号量用于其他类型的同步控制时,非公平排序的吞吐量优势常常超过公平考虑。

这个类还提供了方便的方法来#acquire(int)和#release(int)一次获取或释放多个许可证。

所以,我们在使用这些方法时要注意将公平性参数设置为true,避免造成某些线程被无限期延迟的风险增加。

上面示例的执行结果:

另外:内存一致性效应:调用release()方法之前线程中的操作优先于在另一个线程中跟在一个成功获取acquire()后的行为。

闩锁(CountDownLatch)

CountDownLatch类是JDK中另一个有用的线程同步类。

与信号量类类似,它也提供了一个计数器,但是CountDownLatch的计数器是随着获取许可的线程越多而递减的直到减少到零。

在这过程中它可以阻塞进入的线程等待,等到计数器递减为零,等待的所有线程都可以继续执行。

我们在开发过程中,可能会遇到这样的情况,比如某个应用程序的启动和初始化过程,需要有多个资源同时具备才可以,那么我们通过多个线程来提供这些资源,而必须等待所有资源条件都具备了,应用程序才可以启动执行。如此我们就可以使用这种CountDownLatch来实现。

简单概括来说就是:当池中的所有线程都必须在某个时间点进行同步才能继续运行时,通常需要使用CountDownLatch。

另外一个比较常见的简单的例子是,一个应用程序必须从不同的数据源收集数据,然后才能将新的数据集存储到数据库中。

下面的代码演示了五个线程如何随机休眠一段时间。每一个被唤醒的线程都从锁存器开始计数,然后等待锁存器变为零。

最后,所有线程输出它们已经完成。

运行此示例时,我们将看到输出“等待闩锁”出现在不同的时间点,但是完成每个线程的消息立即打印一个接一个。实现了先到线程等待后到线程到齐,一同开始完成。

循环栅锁(CyclicBarrier)

CyclicBarrier类也实现了一个计数器,从名字中的Cyclic就可以猜到,该计数器具有循环的功能,与CountDownLatch不同,该计数器可以在被计数为零后重置。

所有线程都必须调用其方法await(),直到内部计数器被设置为零。

才会唤醒等待的线程并继续。

同时在内部,计数器被重置为原来的值,整个过程可以重新开始。

这种机制大多用在我们经常需要循环的聚合多种资源重复执行某个动作时采用。

下面是一个简单是示例,来看一下:

图2-1

图 2-1

其执行结果如下:

我们可以看到,在我们指定的时间范围内,三个线程不断的在等待和完成中执行多次。

总结:

上面我们所说的信号量,闩锁,循环栅锁等都是基于无锁的一个原子类型状态表示来实现的,其内在就是一个简单的原子性Int类型状态属性,通过对这个属性的原子读取和增减来作为控制指定数量线程进入临界区操作的许可,从而能够在无锁的情况下,借助CAS操作基础,实现了组线程控制的目的。

总之,只要记住信号量就是一个计数器,它的读取和操作都是原子性的,通过原子性操作的数值来控制线程操作执行。这在现代Java高级并发编程中应用非常广泛,特别是现在充分利用服务器多核并发处理优势的情况下,对于多线程的组控制,已成为开发者必须要了解的重要内容。

信号量的实现和应用实验报告_Java高级编程基础:原子信号量操作实现组线程执行管理...相关推荐

  1. java主类与源代码名称_Java高级编程基础:类文件结构解析,看穿Class代码背后的秘密...

    类文件结构 在说完了JVM内部结构后,接下来我们需要说一下另外一个非常重要的基础概念Class类结构. 我们既然知道了开发的Java源代码会首先被编译成字节码文件保存,JVM的类加载器会读取这些文件内 ...

  2. auto.js停止所有线程_Java多线程编程基础知识 概念介绍,以及线程状态

    一.进程 进程是操作系统结构的基础:是一次程序的执行:是一个程序及其数据在处理机上顺序执行时所发生的活动.操作系统中,几乎所有运行中的任务对应一条进程(Process).一个程序进入内存运行,即变成一 ...

  3. java睡眠后继续执行_Java高级编程基础:如何使用线程的休眠,中断和连接

    线程休眠和中断 我们知道了在编程过程中创建线程,并启动以后,线程会交由操作系统来管理调度执行一个我们指定的计算任务. 如果没有其它异常情况出现的话,它会持续运行直到我们实现的run()方法执行完毕为止 ...

  4. 大一计算机应用的实验报告,大一《计算机应用基础》实验报告1.doc

    大一<计算机应用基础>实验报告1 <计算机应用基础>实验报告 班级:工管**** 学号:201011****** 姓名:*** 实验1 WINDOWS XP 综合练习 一.实验 ...

  5. oracle空间数据库实验报告,Oracle数据库实验报告六 PL/SQL基础

    Oracle数据库实验报告六 PL/SQL基础 -by QQC from BTBU [实验目的] PL/SQL的安装网上有很多教程这里就不做赘述了,如果后序需求大的话我再考虑做一期PL/SQL安装使用 ...

  6. java 168转换成861_java实验-java语言面向对象编程基础

    java实验-java语言面向对象编程基础 (12页) 本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦! 8.90 积分 广州大学学生实验报告广州大学学生实验报告 ...

  7. java程序设计实验报告_JavA程序设计实验报告.doc

    Java程序设计实验报告 学号: 姓名: 座位号: 实验日期: [实验名称]: JDK配置与开发工具的使用 [实验目的]: 熟悉JDK开发环境. 熟悉EditPlus编辑器或Eclipse等开发环境的 ...

  8. java数组实验报告_Java实验一实验报告

    实验内容 1.使用JDK编译.运行简单的Java程序: 2.使用Eclipse编辑.编译.运行.调试Java程序. 实验要求 1.没有Linux基础的同学建议先学习<><>课程 ...

  9. java继承机制实验报告_JAVA继承实验报告.doc

    JAVA继承实验报告 JAVA语言程序设计 实验报告 实验一 实验题目: 面 向 对 象 编 程 指导老师: 卢照 专业班级:计算机科学与技术系1106班 姓 名:*****) 2014年3月20日 ...

最新文章

  1. html景图片怎么设置百分比,8个风光照片拍摄技巧
  2. 关于C和C++的一点观点
  3. JAVA进阶教学之(Object类中的hashCode方法)
  4. 2.6宽带接入技术ADSL
  5. 基础教程之Spin旋转篇
  6. php carbon,laravel Carbon函数
  7. Android -- 背景虚化
  8. 华为OJ-奥运会排行榜C++
  9. html 文件对比,文件内容差异对比方法
  10. linux防火墙 3306端口,Linux配置防火墙 开启80端口、3306端口的方法
  11. 绕过CDN查找真实IP方法总结
  12. 谷歌浏览器被搜狗劫持问题
  13. 51单片机 Proteus仿真 简易计算器设计 清零 十以内
  14. 钱包的下一站:无私钥、不用再抄助记词
  15. 十二大行的金融科技子公司,哪家注册资本最高?
  16. 南开大学计算机考博参考书目,南开大学2017年考博真题回忆汇总帖
  17. 高屋建瓴 08年IT产业38个黄金技术
  18. CentOS 7系统中查看网卡信息
  19. 十一届中国大学生计算机设计大赛,我校在2018年(第十一届)中国大学生计算机设计大赛中再获佳绩...
  20. (已更新)恋爱话术小程序源码下载

热门文章

  1. PYTHON编程导论群问题汇总(五)
  2. 如何防止我的模型过拟合?这篇文章给出了6大必备方法
  3. 大规模神经网络的训练优化入门
  4. 清华硕士眼中的2021届算法岗秋招
  5. 用最骚的话让你彻底弄懂贝叶斯!
  6. 【漫画】以后在有面试官问你AVL树,你就把这篇文章扔给他。
  7. K-近邻算法之案例2:预测facebook签到位置
  8. Servlet 请求的转发
  9. ospf的四种网络类型
  10. ACMNO.14一球从M米高度自由下落,每次落地后返回原高度的一半,再落下。 它在第N次落地时反弹多高?共经过多少米? 保留两位小数 输入 M N 输出 它在第N次落地时反弹多高?共经过多少米