前言

java多线程之间进行通信时,JDK主要提供了以下几种通信工具类。主要有Semaphore、CountDownLatch、CyclicBarrier、exchanger、Phaser这几个通讯类。下面我们来详细介绍每个工具类的作用、原理及用法。

Semaphore介绍

Semaphore翻译过来是信号的意思。顾名思义,这个工具类提供的功能就是多个线程彼此“打信号”。而这个“信号”是一个int类型的数据,也可以看成是一种“资源”,用来限定线程访问该资源的数量。

它的构造函数有两个,一个参数的用来指定线程访问资源的数量;两个参数的一个用来指定线程访问资源的数量,一个用来指定是否为公平锁。关于公平锁非公平锁的概念请参照文章java并发编程系列之原理篇-synchronized与锁。构造函数代码如下:


// 默认情况下使用非公平
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

它的最主要的两个方法是acquire()和release()。acquire()方法会申请一个permit,而release方法会释放一个permit。当然,你也可以申请多个acquire(int permits)或者释放多个release(int permits)。每次acquire,permits就会减少一个或者多个。如果减少到了0,再有其他线程来acquire,那就要阻塞这个线程直到有其它线程release permit为止。

Semaphore的使用

Semaphore主要用来控制线程访问资源的数量的场景。举个例子,在并发条件下,我只想让3个线程来执行某一任务。请看示例代码:

public class SemaphoreDemo {

    public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i < 10; i++) {
new Thread(new MyThread(i,semaphore)).start();
}
} static class MyThread implements Runnable{ private int id;//线程的ID号
private Semaphore semaphore; public MyThread(int id, Semaphore semaphore){
this.id = id;
this.semaphore = semaphore;
} @Override
public void run() {
try {
//获取信号量permit许可
semaphore.acquire();
//接下来可以用来执行具体的线程任务
System.out.println(String.format("当前的线程是%d,还剩有%d个线程资源可以使用,有%d个线程处于等待中。",
id,semaphore.availablePermits(),semaphore.getQueueLength()));
Random random = new Random();
//随机睡眠时间,打乱释放顺序
Thread.sleep(random.nextInt(1000));
System.out.println(String.format("线程%d释放了资源",id));
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//任务结束,释放资源
semaphore.release();
}
}
}
}

输出结果:

当前的线程是1,还剩有1个线程资源可以使用,有0个线程处于等待中。

当前的线程是0,还剩有2个线程资源可以使用,有0个线程处于等待中。

当前的线程是2,还剩有0个线程资源可以使用,有0个线程处于等待中。

线程2释放了资源

当前的线程是3,还剩有0个线程资源可以使用,有6个线程处于等待中。

线程1释放了资源

当前的线程是4,还剩有0个线程资源可以使用,有5个线程处于等待中。

线程0释放了资源

当前的线程是5,还剩有0个线程资源可以使用,有4个线程处于等待中。

线程3释放了资源

当前的线程是6,还剩有0个线程资源可以使用,有3个线程处于等待中。

线程4释放了资源

当前的线程是7,还剩有0个线程资源可以使用,有2个线程处于等待中。

线程5释放了资源

当前的线程是8,还剩有0个线程资源可以使用,有1个线程处于等待中。

线程8释放了资源

当前的线程是9,还剩有0个线程资源可以使用,有0个线程处于等待中。

线程7释放了资源

线程6释放了资源

线程9释放了资源

从结果可以看出来,最初抢到这3个资源的线程是1,0,2,而其他线程进入了等待队列。之后每当有一个线程释放了该资源,才会有其他在等待队列的线程抢到资源。Semaphore默认的acquire方法是会让线程进入等待队列,且会抛出中断异常。但它还有一些方法可以忽略中断或不进入阻塞队列:

 // 忽略中断
public void acquireUninterruptibly()
public void acquireUninterruptibly(int permits) // 不进入等待队列,底层使用CAS
public boolean tryAcquire
public boolean tryAcquire(int permits)
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException
public boolean tryAcquire(long timeout, TimeUnit unit)

Semaphore的原理

Semaphore内部有一个继承了AQS的同步器Sync成员变量,重写了tryAcquireShared方法。在这个方法里,会去尝试获取资源。如果获取失败(想要的资源数量小于目前已有的资源数量),就会返回一个负数(代表尝试获取资源失败)。然后当前线程就会进入AQS的等待队列。具体的代码逻辑请查看JDK1.8中java.util.concurrent包下的Semaphore类。

参考链接

在这里很感谢能够有幸看到来自各个大厂大神们的开源项目深入浅出Java多线程,让我对多线程的知识有一个更深层次的了解。

java并发编程系列原理篇--JDK中的通信工具类Semaphore的更多相关文章

  1. java并发编程实战《二十一》无锁工具类

    不安全的累加代码,如下 1 public class Test { 2 long count = 0; 3 void add10K() { 4 int idx = 0; 5 while(idx++ & ...

  2. java并发学习--第七章 JDK提供的线程工具类

    一.ThreadLocal ThreadLocal类用于隔离多线程中使用的对象,为ThreadLocal类中传递的泛型就是要隔离的对象,简单的来说:如果我们在主线程创建了一个对象,并且需要给下面的多线 ...

  3. 原创】Java并发编程系列2:线程概念与基础操作

    [原创]Java并发编程系列2:线程概念与基础操作 伟大的理想只有经过忘我的斗争和牺牲才能胜利实现. 本篇为[Dali王的技术博客]Java并发编程系列第二篇,讲讲有关线程的那些事儿.主要内容是如下这 ...

  4. Java并发编程系列-(9) JDK 8/9/10中的并发

    9.1 CompletableFuture CompletableFuture是JDK 8中引入的工具类,实现了Future接口,对以往的FutureTask的功能进行了增强. 手动设置完成状态 Co ...

  5. Java并发编程系列-(8) JMM和底层实现原理

    8. JMM和底层实现原理 8.1 线程间的通信与同步 线程之间的通信 线程的通信是指线程之间以何种机制来交换信息.在编程中,线程之间的通信机制有两种,共享内存和消息传递. 在共享内存的并发模型里,线 ...

  6. [ 高并发]Java高并发编程系列第二篇--线程同步

    高并发,听起来高大上的一个词汇,在身处于互联网潮的社会大趋势下,高并发赋予了更多的传奇色彩.首先,我们可以看到很多招聘中,会提到有高并发项目者优先.高并发,意味着,你的前雇主,有很大的业务层面的需求, ...

  7. Java并发编程系列-(4) 显式锁与AQS

    4 显示锁和AQS 4.1 Lock接口 核心方法 Java在java.util.concurrent.locks包中提供了一系列的显示锁类,其中最基础的就是Lock接口,该接口提供了几个常见的锁相关 ...

  8. 干货:Java并发编程系列之volatile(二)

    接上一篇<Java并发编程系列之synchronized(一)>,这是第二篇,说的是关于并发编程的volatile元素. Java语言规范第三版中对volatile的定义如下:Java编程 ...

  9. Java并发编程系列-(5) Java并发容器

    5 并发容器 5.1 Hashtable.HashMap.TreeMap.HashSet.LinkedHashMap 在介绍并发容器之前,先分析下普通的容器,以及相应的实现,方便后续的对比. Hash ...

随机推荐

  1. 开箱即用,Knative 给您极致的容器 Serverless 体验

    作者 | 冬岛  阿里巴巴技术专家 导读:托管 Knative 开箱即用,您不需要为这些常驻实例付出任何成本.结合 SLB 云产品提供 Gateway 的能力以及基于突发性能型实例的保留规格功能,极大 ...

  2. JavaScript Basic

    Exercise-1 Write a JavaScript program to display the current day and time in the following format. T ...

  3. 对于近似有序序列(即除掉少数K个元素后是有序序列且K<<n),试分析直接插入排序,冒牌排序和简单选择排序的时间复杂度

    学弟问的一道数据结构的题,关于一些排序算法的时间复杂度. 针对近似有序序列, ①当使用直接插入排序时,其基本操作为数组中元素的移动.最好情况下,待排序列有序,无需移动,此时时间复杂度为O(n), 当为 ...

  4. Flutter 使用Navigator进行局部跳转页面

    老孟导读:Navigator组件使用的频率不是很高,但在一些场景下非常适用,比如局部表单多页填写.底部导航一直存在,每个tab各自导航场景. Navigator 是管理路由的控件,通常情况下直接使用N ...

  5. 【Java】向*.txt文档里面重复添加同一个字符串

    闺蜜说让我用代码写五万个对不起给她~~ import java.io.FileWriter; import java.io.IOException; /** * Created by lenovo o ...

  6. Win10企业版远程桌面结合frp实现公网远程

    Win10企业版远程桌面结合frp实现公网远程 前言 由于经常下班后还要处理问题,但是又没有运维那么频繁,就不想天天背着电脑来回跑,刚开始用的teamviewer,后来被商业劝退了(就是不让用了,让买 ...

  7. SpringBoot打包Docker镜像

    构建spring boot项目 本地测试访问 打成jar包 在本地运行jar包测试 到这一步就证明jar包没问题 idea下载一个插件 在这创建一个Dockerfile文件 安装插件后会高亮显示. 在 ...

  8. Mybatis执行器源码手记

    今天将Mybatis的执行器部分做一下简单手记. 一.java原生JDBC 众所周知,Mybatis是一个半自动化ORM框架.其实说白了,就是将java的rt.jar的JDBC操作进行了适度的封装.所 ...

  9. 跟着阿里学JavaDay04——Java基础语法(二)

    一.Java运算符 运算符的简介 所有的程序开发,都是对数字的进行处理.而对数字的处理,一定会存在所谓的操作模式,而这些操作模式就被称为运算符. 算术运算符 对于算术运算符,我们主要掌握++,--的运 ...

  10. ES6-变量let和常量const

    1.以往js变量 var 1.可以重复声明 2.无法限制修改(指不能声明常量) 3.没有块级作用域(指{}这样的) 2.现在ES6变量 let 不能重复声明-变量,可以修改,块级作用域 const 不 ...