Semaphore(信号量)是一个线程同步结构,用于在线程间传递信号,以避免出现信号丢失,或者像锁一样用于保护一个关键区域。自从 5.0 开始,jdk 在 java.util.concurrent 包里提供了 Semaphore 的官方实现,因此大家不需要自己去实现 Semaphore。

  1. 简单的 Semaphore 实现
  2. 使用 Semaphore 来发出信号
  3. 可计数的 Semaphore
  4. 有上限的 Semaphore
  5. 把 Semaphore 当锁来使用

简单的 Semaphore 实现

下面是一个信号量的简单实现:

public class Semaphore {

private boolean signal = false;

public synchronized void take() {

this.signal = true;

this.notify();

}

public synchronized void release() throws InterruptedException{

while(!this.signal) wait();

this.signal = false;

}

}

Take 方法发出一个被存放在 Semaphore 内部的信号,而 Release 方法则等待一个信号,当其接收到信号后,标记位 signal 被清空,然后该方法终止。

使用这个 semaphore 可以避免错失某些信号通知。用 take 方法来代替 notify,release 方法来代替 wait。如果某线程在调用 release 等待之前调用 take 方法,那么调用 release 方法的线程仍然知道 take 方法已经被某个线程调用过了,因为该 Semaphore 内部保存了 take 方法发出的信号。而 wait 和 notify 方法就没有这样的功能。

当用 semaphore 来产生信号时,take 和 release 这两个方法名看起来有点奇怪。这两个名字来源于后面把 semaphore 当做锁的例子,后面会详细介绍这个例子,在该例子中,take 和 release 这两个名字会变得很合理。

使用 Semaphore 来产生信号

下面的例子中,两个线程通过 Semaphore 发出的信号来通知对方

Semaphore semaphore = new Semaphore();

SendingThread sender = new SendingThread(semaphore);

ReceivingThread receiver = new ReceivingThread(semaphore);

receiver.start();

sender.start();

public class SendingThread {

Semaphore semaphore = null;

public SendingThread(Semaphore semaphore){

this.semaphore = semaphore;

}

public void run(){

while(true){

//do something, then signal

this.semaphore.take();

}

}

}

public class RecevingThread {

Semaphore semaphore = null;

public ReceivingThread(Semaphore semaphore){

this.semaphore = semaphore;

}

public void run(){

while(true){

this.semaphore.release();

//receive signal, then do something...

}

}

}

可计数的 Semaphore

上面提到的 Semaphore 的简单实现并没有计算通过调用 take 方法所产生信号的数量。可以把它改造成具有计数功能的 Semaphore。下面是一个可计数的 Semaphore 的简单实现。

public class CountingSemaphore {

private int signals = 0;

public synchronized void take() {

this.signals++;

this.notify();

}

public synchronized void release() throws InterruptedException{

while(this.signals == 0) wait();

this.signals--;

}

}

有上限的 Semaphore

上面的 CountingSemaphore 并没有限制信号的数量。下面的代码将 CountingSemaphore 改造成一个信号数量有上限的 BoundedSemaphore。

public class BoundedSemaphore {

private int signals = 0;

private int bound   = 0;

public BoundedSemaphore(int upperBound){

this.bound = upperBound;

}

public synchronized void take() throws InterruptedException{

while(this.signals == bound) wait();

this.signals++;

this.notify();

}

public synchronized void release() throws InterruptedException{

while(this.signals == 0) wait();

this.signals--;

this.notify();

}

}

在 BoundedSemaphore 中,当已经产生的信号数量达到了上限,take 方法将阻塞新的信号产生请求,直到某个线程调用 release 方法后,被阻塞于 take 方法的线程才能传递自己的信号。

把 Semaphore 当锁来使用

当信号量的数量上限是 1 时,Semaphore 可以被当做锁来使用。通过 take 和 release 方法来保护关键区域。请看下面的例子:

BoundedSemaphore semaphore = new BoundedSemaphore(1);

...

semaphore.take();

try{

//critical section

} finally {

semaphore.release();

}

在前面的例子中,Semaphore 被用来在多个线程之间传递信号,这种情况下,take 和 release 分别被不同的线程调用。但是在锁这个例子中,take 和 release 方法将被同一线程调用,因为只允许一个线程来获取信号(允许进入关键区域的信号),其它调用 take 方法获取信号的线程将被阻塞,知道第一个调用 take 方法的线程调用 release 方法来释放信号。对 release 方法的调用永远不会被阻塞,这是因为任何一个线程都是先调用 take 方法,然后再调用 release。

通过有上限的 Semaphore 可以限制进入某代码块的线程数量。设想一下,在上面的例子中,如果 BoundedSemaphore 上限设为 5 将会发生什么?意味着允许 5 个线程同时访问关键区域,但是你必须保证,这个 5 个线程不会互相冲突。否则你的应用程序将不能正常运行。

必须注意,release 方法应当在 finally 块中被执行。这样可以保在关键区域的代码抛出异常的情况下,信号也一定会被释放。

java多线程-信号量的更多相关文章

  1. java多线程--信号量Semaphore的使用

    Semaphore可以控制某个共享资源可被同时访问的次数,即可以维护当前访问某一共享资源的线程个数,并提供了同步机制.例如控制某一个文件允许的并发访问的数量. 例如网吧里有100台机器,那么最多只能提 ...

  2. Java多线程信号量同步类CountDownLatch与Semaphore

    信号量同步是指在不同线程之间,通过传递同步信号量来协调线程执行的先后次序.CountDownLatch是基于时间维度的Semaphore则是基于信号维度的. 1:基于执行时间的同步类CountDown ...

  3. Java多线程系列--“JUC锁”11之 Semaphore信号量的原理和示例

    概要 本章,我们对JUC包中的信号量Semaphore进行学习.内容包括:Semaphore简介Semaphore数据结构Semaphore源码分析(基于JDK1.7.0_40)Semaphore示例 ...

  4. Java多线程并发工具类-信号量Semaphore对象讲解

    Java多线程并发工具类-Semaphore对象讲解 通过前面的学习,我们已经知道了Java多线程并发场景中使用比较多的两个工具类:做加法的CycliBarrier对象以及做减法的CountDownL ...

  5. Java多线程的信号量

    Java的信号量主要的作用是控制线程对资源的访问例如我一个线程池里面有100个线程等待执行,但是我允许最多同时运行5个线程,这5个线程只有其中一个线程执行完毕后,在线程池中等待的线程才能进入开始执行, ...

  6. 40个Java多线程问题总结

    前言 Java多线程分类中写了21篇多线程的文章,21篇文章的内容很多,个人认为,学习,内容越多.越杂的知识,越需要进行深刻的总结,这样才能记忆深刻,将知识变成自己的.这篇文章主要是对多线程的问题进行 ...

  7. java从基础知识(十)java多线程(上)

    线程,有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元.另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点 ...

  8. Java多线程系列--“JUC锁”01之 框架

    本章,我们介绍锁的架构:后面的章节将会对它们逐个进行分析介绍.目录如下:01. Java多线程系列--“JUC锁”01之 框架02. Java多线程系列--“JUC锁”02之 互斥锁Reentrant ...

  9. Java多线程系列目录(共43篇)

    最近,在研究Java多线程的内容目录,将其内容逐步整理并发布. (一) 基础篇 01. Java多线程系列--“基础篇”01之 基本概念 02. Java多线程系列--“基础篇”02之 常用的实现多线 ...

随机推荐

  1. c#属性中的get和set属性

    get是给属性赋值,set是取属性的值. get.set用法: 一是隐藏组件或类内部的真是成员: 二是用来建立约束的,比如,实现“有我没你”这种约束: 三是用来响应属性变化事件,当属性变化是做某事,只 ...

  2. HTML5系列:HTML5本地存储

    1. Web Storage存储 HTML4在客户端存储数据通常使用Cookie存储机制将数据保存在用户的客户端,但使用Cookie方式存储客户端数据存在一系列的制约发展因素,如限制存储数据空间大小. ...

  3. CSS系列:CSS选择器

    选择器(selector)是CSS中很重要的概念,所有HTML语言中的标记样式都是通过不同的CSS选择器来控制的.用户只需要通过选择对不同的HTML标签进行选择,并赋予各种样式声明,即可实现各种效果. ...

  4. 轻量级前端MVVM框架avalon - 执行流程2

    接上一章 执行流程1 在这一大堆扫描绑定方法中应该会哪些实现? 首先我们看avalon能帮你做什么? 数据填充,比如表单的一些初始值,切换卡的各个面板的内容({{xxx}},{{xxx|html}}, ...

  5. Topology and Geometry in OpenCascade-Adapters

    Topology and Geometry in OpenCascade-Adapters eryar@163.com 摘要Abstract:本文简要介绍了适配器模式(adapter pattern) ...

  6. IOS学习之初识KVO

    什么是KVO? KVO(Key-Value Observing)键值观察,是一种通过对对象的某一个属性添加观察者,一旦这个属性值发生变化,就会通知当前观察者的一种机制. 该如何使用? 1.注册,指定被 ...

  7. ASP.NET Core的配置(4):多样性的配置来源[中篇]

    我们在本篇文章中会介绍三种针对物理文件的ConfiguationProvider,它们分别是针对JSON文件的JsonConfiguationProvider,针对XML文件的XmlConfiguat ...

  8. c# 枚举

    命名空间:   System程序集:  mscorlib(mscorlib.dll 中) 定义一个枚举类型 public enum Week { [Description("星期一" ...

  9. 如何设置SharePoint 2013 的根网站集下的“更改此术语的目标页面”

    起因: 首先看问题截图Figure 1,在术语驱动的页面中设置更改此术语的目标页面,会被警告“该URL 不指向某个页面”,原因是我所找到的这个目标页面是一个非aspx结尾的URL链接. Figure ...

  10. PC网站CSS分享

    这里想总结些编写网页的经验,以PC端的为主,前面总结过后台的,今天写些前台的,参考了bootstrap3.3.5.bootstrip2.3.2和模版matrix. 前段时间还写过一篇<前端基础学 ...