目录结构

一、简介
1.1、Semaphore
1.2、CountDownLatch
1.3、CyclicBarrier
二、信号量Semaphore
2.1、构造方法
2.2、主要方法
2.3、示例代码
三、计数器CountDownLatch
3.1、主要方法
3.2、示例代码1
3.3、示例代码2
四、栅栏类CyclicBarrier
4.1、构造方法
4.2、示例代码

一、简介

1.1、 Semaphore

信号量,见文知义,常用于并发控制中的限流作用,我理解是限定数量的共享锁机制。该共享资源最多同时可让n个线程访问,超过n个线程就阻塞等待,如有资源空闲, 唤醒其他等待线程(唤醒又分公平与非公平,默认非公平)比如一条四车道大桥,每次仅能并发通过4辆汽车,而在高峰期时100辆车涌入,这次需要一个信号灯来限制车辆,每次最多放行4辆车,在车辆通过后再放行。在并发环境下,每辆车就是如一个线程,4车道大桥就如有限的资源,需要控制线程的数量,在这种业务场景下,靠锁同步的机制(如synchronized)力有不逮,java并发包中提供Semaphore类可以帮助解决此类场景。

应用场景:
1、资源控制:控制数据库连接数,如有多个IO操作,每个需要操作数据库写入,数据库连接数有限,控制连接数据库数量。
例:100个线程执行IO,只有10个mysql连接,最多同时可以有10个线程获取到连接,否则会报错无法获取连接,这时可用信号量控制。 2、可当同步锁使用,设置信号量通道等于1。

1.2、CountDownLatch

允许一个或多个线程等待其他线程完成操作后再执行。其内部维护一个计数器,设置初始值给state,每调用 countDown()方法一次,state数量减1,调用await()方法的线程被阻塞,需要等待state减少为0时才可被唤醒继续执行。

应用场景:
1、一个任务要统计公司一星期的财务流水总额,每次需要读取5张Excel表统计流水汇总,如何快速地统计出来?
可以使用CountDownLatch,先开始5个线程并发地分别统计每张表的流水额度总和,当5个线程统计结束,再汇总总额。 2、开发对外接口,要求响应快,而该接口内部逻辑复杂,涉及多个服务的调用,并依赖这些独立服务响应结果进行下一步操作,这时可以考虑CountDownLatch或者 CyclicBarrier,并发调用多个服务,获取这些结果后才进行下一步,缩短处理时间。

1.3、CyclicBarrier

循环屏障,栅栏类; CyclicBarrier 可让多个线程相互等待,当所有线程都达到后再唤醒所有线程继续执行。如导游设定目标点,所有游客到这集合,先到的游客等待其他游客,当所有的游客都到了后,大家再一起出发。

CyclicBarrier与CountDownLatch区别:
1) CyclicBarrier 在应用场景中,多个线程之间相互等待,线程之间在业务上可能会更有依赖性 ; CountDownLatch是每个执行 coutDown方法的线程之间可以没有依赖性,而是执行await方法的线程更依赖这些执行coutDown的线程。 2)CountDownLatch计数器只能使用一次,Semaphore可以多次使用,可以重置使用。 3)CyclicBarrier 可以处理更复杂的业务场景,如线程都达到屏障后,可以在构造函数中 CyclicBarrier(int parties, Runnable barrierAction) 传入线程barrierAction,当达到屏障触发条件时,可以比其他等待线程优先执行,处理业务。 4)CyclicBarrier还有很多方法,如查看当前到达屏障被阻塞的线程数量 getNumberWaiting()。 5)CountDownLatch 是每个线程执行完减1操作,当计数器为0时,才唤醒等待线程,阻塞线程只有1个或者多个;CyclicBarrier是让所有线程到达屏障处就被阻塞了,当所有线程都到达时,唤醒所有被阻塞线程继续执行,阻塞线程有多个,线程之间是相互等待。

二、信号量Semaphore

2.1、构造方法

Semaphore(int permits) //默认非公平
//是否公平尝试获取许可证
Semaphore(int permits, boolean fair)

2.2、主要方法

acquire() //从信号量那去获取一个许可,如果没有剩余的就被阻塞
acquire(int permits)//也可一次获取多个许可
release() //释放一个许可,将其返还给信号量,给其他线程使用。
release(int permits) //一次释放多个许可

2.3、示例代码

package Semaphore;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* @author zdd
* 2019/11/27 6:43
* Description: 信号量测试
*/
public class SemaphoreService {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); private Semaphore semaphore = new Semaphore(4); public void doSomeThing() throws InterruptedException {
//1,获取一个许可
semaphore.acquire(); System.out.println(Thread.currentThread().getName() + "--start--" +getFormatDate());
// 停顿1s
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName() + "--end--" +getFormatDate());
//2,用完释放一个许可,可供其他线程使用
semaphore.release();
}
public static void main(String[] args) {
SemaphoreService semaphoreService = new SemaphoreService();
for (int i = 0; i < 10; i++) {
WorkThread workThread = new WorkThread(i+"",semaphoreService);
workThread.start();
}
} public String getFormatDate() {
return sdf.format(new Date());
}
// 工作线程类 - 静态内部类
static class WorkThread extends Thread { SemaphoreService semaphoreService;
//构造参数传入线程名称及SemaphoreService
public WorkThread(String name, SemaphoreService semaphoreService) {
this.semaphoreService = semaphoreService;
setName(name);
}
@Override
public void run() {
try {
semaphoreService.doSomeThing();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

1.线程最多4个同时执行

//1,设置信号量数为4,执行结果如下图
private Semaphore semaphore = new Semaphore(4);

2.线程依次执行,实现同步

//1,设置信号量数为1,执行结果如下图
private Semaphore semaphore = new Semaphore(1);

三、计数器CountDownLatch

3.1、主要方法

countDown() 计数器减1
await() 阻塞等待,直到计数器为0唤醒继续执行
await(long timeout, TimeUnit unit) 阻塞等待,在等待设定时间计时器还没到减为0,也不会再继续等待了。

3.2、示例代码1

package countDownLatch;

import javax.swing.plaf.IconUIResource;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
* @author zdd
* 2019/11/29 7:19 下午
* Description: 班长召集士兵集合拉练案例
*/
public class CountDownLatchSoldierTest { private static final Integer THREAD_COUNT = 10;
//等待士兵10人
private static CountDownLatch countDownLatch = new CountDownLatch(10); public static void main(String[] args) throws InterruptedException {
//1,开10个线程模拟士兵签到,采用线程池创建
ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
for (int i = 0; i < 10; i++) {
//2,提交10个线程执行
executorService.submit(()->{
try{
System.out.println(Thread.currentThread().getName()+ ": 士兵报告");
}finally {
//3,计数器减1
countDownLatch.countDown();
}
});
}
//3,主线程做班长,负责等待所有士兵到齐,开始拉练
countDownLatch.await();
System.out.println("班长: 集合完毕,开始5公里越野! "); //4,关闭线程池
executorService.shutdown();
}
}

执行结果如下:

3.3、示例代码2

计算器未减到0,主线程持续等待。

package countDownLatch;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; /**
* zdd
* 2019/11/5 10:50
* Description:测试计数器使用
*/
public class CountDownLatchTest { static int number = 2;
//1,设置计数器的值为 2
static CountDownLatch countDownLatch = new CountDownLatch(number); public static void main(String[] args) throws InterruptedException {
//2,开启一个线程,传入计数器
new Thread(()-> {
try {
//睡2s,模拟执行业务
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("调用countDown前, 当前count 值为:"+countDownLatch.getCount());
//3,调用递减 1次
countDownLatch.countDown();
System.out.println("调用countDown后, 当前count 值为:"+countDownLatch.getCount());
}
},"worker").start(); //情况1、主线程开始执行 ,等待1s之后,如果还没到条件,也不等了哦
// countDownLatch.await(1,TimeUnit.SECONDS);
//情况2、阻塞等待,如果计数未到0,一直阻塞等待
countDownLatch.await();
System.out.println("主线程继续执行");
}
}

主线程阻塞等待,执行结果如下:

四、栅栏类CyclicBarrier

4.1、构造方法

CyclicBarrier(int parties) //parties为需要等待线程的数量
//barrierAction,在所有其他线程到达后,优先执行的线程。可根据业务添加
CyclicBarrier(int parties, Runnable barrierAction)

4.2、示例代码

package CyclicBarrier;

import java.util.Map;
import java.util.concurrent.*;
/**
* @author zdd
* 2019/11/28 9:20
* Description: 开启4个线程分别统计sheet表,使用栅栏类实现同步统计计算,最后计算总和;此处也可用
* CountDownLatch实现
*/
public class BankAccountService {
private ConcurrentHashMap<String,Integer> concurrentHashMap = new ConcurrentHashMap<>(); // 线程数
private final static int THREAD_COUNT = 4;
private CyclicBarrier cyclicBarrier = new CyclicBarrier(4, new Runnable() {
@Override
public void run() {
Integer sumcount = 0;
//汇总每个sheet计算结果
for (Map.Entry<String, Integer> entry: concurrentHashMap.entrySet()) {
sumcount+=entry.getValue();
}
System.out.println("优先执行, 求和计算完毕,总和为:"+ sumcount);
}
}); public void count() {
ExecutorService executor = Executors.newFixedThreadPool(4); for (int i = 0; i < THREAD_COUNT; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"到达屏障!");
concurrentHashMap.put(Thread.currentThread().getName(),1); try {
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName()+"继续执行!");
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
executor.shutdown();
} public static void main(String[] args) {
BankAccountService bankAccountService = new BankAccountService();
bankAccountService.count();
}
}

运行结果如下:

// 将线程数改为2,阻塞等待其他线程
private final static int THREAD_COUNT = 2;


参考资料:

Java并发编程的艺术-方腾飞

多线程之美3一Java并发工具类的更多相关文章

  1. 基于AQS实现的Java并发工具类

    本文主要介绍一下基于AQS实现的Java并发工具类的作用,然后简单谈一下该工具类的实现原理.其实都是AQS的相关知识,只不过在AQS上包装了一下而已.本文也是基于您在有AQS的相关知识基础上,进行讲解 ...

  2. 25.大白话说java并发工具类-CountDownLatch,CyclicBarrier,Semaphore,Exchanger

    1. 倒计时器CountDownLatch 在多线程协作完成业务功能时,有时候需要等待其他多个线程完成任务之后,主线程才能继续往下执行业务功能,在这种的业务场景下,通常可以使用Thread类的join ...

  3. Java并发工具类 - CountDownLatch

    Java并发工具类 - CountDownLatch 1.简介 CountDownLatch是Java1.5之后引入的Java并发工具类,放在java.util.concurrent包下面 http: ...

  4. Java并发工具类CountDownLatch源码中的例子

    Java并发工具类CountDownLatch源码中的例子 实例一 原文描述 /** * <p><b>Sample usage:</b> Here is a pai ...

  5. java 并发工具类CountDownLatch & CyclicBarrier

    一起在java1.5被引入的并发工具类还有CountDownLatch.CyclicBarrier.Semaphore.ConcurrentHashMap和BlockingQueue,它们都存在于ja ...

  6. JAVA并发工具类---------------(CountDownLatch和CyclicBarrier)

    CountDownLatch是什么 CountDownLatch,英文翻译为倒计时锁存器,是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待. 闭锁可以延迟线程的进 ...

  7. 【Java并发工具类】Semaphore

    前言 1965年,荷兰计算机科学家Dijkstra提出的信号量机制成为一种高效的进程同步机制.这之后的15年,信号量一直都是并发编程领域的终结者.1980年,管程被提出,成为继信号量之后的在并发编程领 ...

  8. 【Java并发工具类】Java并发容器

    前言 Java并发包有很大一部分都是关于并发容器的.Java在5.0版本之前线程安全的容器称之为同步容器.同步容器实现线程安全的方式:是将每个公有方法都使用synchronized修饰,保证每次只有一 ...

  9. Java并发工具类(一):等待多线程完成的CountDownLatch

    作用 CountDownLatch是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行 简介 CountDownLatch是在java1.5被引入的,存在于java.uti ...

随机推荐

  1. Linux 修改网卡名

    1. 修改网卡配置文件 vim /etc/sysconfig/network-scripts/ifcfg-ens32 (“ens32”为当前网卡名) 将NAME.DEVICE项修改为eth0 2.  ...

  2. Openmp编程练习

    火车卖票 // OpenMP2.cpp : 定义控制台应用程序的入口点. // #include "stdio.h" #include "omp.h" #inc ...

  3. Ubuntu18.04系统下安装Pycharm&vim设置自动缩进及默认显示行号

    Ubuntu18.04系统自带python3.6及python2.7,Pycharm是一款非常强大的IDE.目前Pycharm有两个版本:专业版和Community社区,区别是专业版是收费,而且功能更 ...

  4. CSPS模拟 58

    爆炸 没算内存见祖宗 为什么偏偏这次卡内存我没算 T1 HashMap各种水 T2 智障背包!但是卡内存! T3 Dashspeed 考试用点分治+线段树水到了80 实际上是个没见过的套路题 在之前的 ...

  5. 一张图牢记vim常用命令

    1.示例(前提要切到命令状态) ^  光标移动到行首 $  光标移动到行尾 set nu 显示行号 :%s/foo/bar/g 会在全局范围(%)查找foo并替换为bar,所有出现都会被替换(g) 参 ...

  6. Java基础语法03-数组

    四数组 数组概念: 数组就是用于存储数据的长度固定的容器,多个数据的数据类型要一致. 百科:数组(array),就是相同数据类型的元素按一定顺序排列的集合,就是把有限个类型相同的变量用一个名字命名,以 ...

  7. 品优购(IDEA版)-第二天

    品优购-第2天 学习目标 目标1:运用AngularJS前端框架的常用指令 目标2:完成品牌管理的列表功能 目标3:完成品牌管理的分页列表功能 目标4:完成品牌管理的增加功能 目标5:完成品牌管理的修 ...

  8. Linux centos5.6版本下mysql5.6主从环境安装配置

    MySQL数据库支持数据库的主从复制功能,因此在集群方面具有其独特的优势,国内外大型网站架构体系中,均采用了MySQL的主从数据库配置来实现查询负载.数据库热备等功能.本人在此将如何配置实现做了个简单 ...

  9. sqlite修改表、表字段等与sql server的不同之处

    sqlite中只支持 ALTER TABLE 命令的 RENAME TABLE 和 ADD COLUMN. 其他类型的 ALTER TABLE 操作如 DROP COLUMN,ALTER COLUMN ...

  10. linux系统LAMP环境部署

    一.安装虚拟机 二.安装CentOS7 注意:以下安装,用的root权限. 三.安装Apache 1.安装 yum -y install httpd 2.开启apache服务 systemctl st ...