Java并发工具类---ForkJoin、countDownlatch、CyclicBarrier、Semaphore

一、Fork Join

fork join是JDK7引入的一种并发框架,采用分而治之的思想来处理并发任务

ForkJoin框架底层实现了工作窃取,当一个线程完成任务处于空闲状态时,会窃取其他工作线程的任务来做,这样可以充分利用线程来进行并行计算,减少线程竞争。但是在某些情况下也会存在竞争。

Fork Join框架局限性
1.拆分任务中不应该去执行IO操作
2.任务不能检查抛出异常,必须通过必要的代码来抛出异常。这个在源码中就可以体现,很多地方都是通过代码主动抛出异常。
3.任务只能使用Fork和Join操作来进行同步机制,如果使用了其他同步机制,则在同步操作时,工作线程就不能执行其他任务了。比如,在Fork/Join框架中,使任务进行了睡眠,那么,在睡眠期间内,正在执行这个任务的工作线程将不会执行其他任务了。

Demo:
ForkJoin进行累加计算

public class MakeArray {
	public static final int ARRAY_LENGTH=4000;

	//获取一个随机数的数组
	public static int[] makeArray(){

		Random r=new Random();
		int[] res=new int[ARRAY_LENGTH];
		for(int i=0;i<ARRAY_LENGTH;i++){
			res[i]=r.nextInt(ARRAY_LENGTH*3);
		}
		return res;
	}
}

public class SumArray {
	private static class SumTask extends RecursiveTask<Integer>{

		private final static int THRESHOLD=MakeArray.ARRAY_LENGTH/10;
		private int[] src;
		private int fromIndex;
		private int toIndex;

		public SumTask(int[] src, int fromIndex, int toIndex) {
			this.src = src;
			this.fromIndex = fromIndex;
			this.toIndex = toIndex;
		}

		@Override
		protected Integer compute() {
			if(toIndex-fromIndex<THRESHOLD){
				//无需再拆分
				int count=0;
				for(int i=fromIndex;i<=toIndex;i++){
					try {
						TimeUnit.MILLISECONDS.sleep(1);
						count+=src[i];
					} catch (InterruptedException e) {
						throw new RuntimeException(e);
					}

				}
				return count;
			}else{
				int mid=(fromIndex+toIndex)/2;
				SumTask left=new SumTask(src,fromIndex,mid);
				SumTask right=new SumTask(src,mid+1,toIndex);
				invokeAll(left,right); //执行任务,把任务添加到队列,该方法中执行了fork
				return left.join()+right.join(); //合并结果
			}


		}
	}

	public static void main(String[] args) {
		int[] src=MakeArray.makeArray();
		ForkJoinPool pool=new ForkJoinPool();
		SumTask innerFind=new SumTask(src,0,src.length-1);
		long start=System.currentTimeMillis();
		pool.invoke(innerFind);
		System.out.println("The count is "+innerFind.join()+" spend time:"+(System.currentTimeMillis()-start)+" ms");
	}
}

运行结果:
在这里插入图片描述
采用单线程进行对比:

public class SumNormal {

	public static void main(String[] args) {
		int count=0;
		int[] src=MakeArray.makeArray();
		long start=System.currentTimeMillis();
		for(int i=0;i<src.length;i++){
			try {
				TimeUnit.MILLISECONDS.sleep(1);
				count+=src[i];
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
		}
		System.out.println("The count is "+count
			+" spend time:"+(System.currentTimeMillis()-start)+"ms");
	}
}

在这里插入图片描述

二、countDownlatch

在这里插入图片描述
countDownlatch也是一个java的同步工具类,它通过计数器来控制线程的执行顺序。初始化时需要初始化计数器的值,一般都是线程数量。每当一个线程执行完任务,计数器减一,当计数器为0,等待的线程就可以恢复执行任务。

需注意: 计数器的值不一定就是线程数量,线程中可以多次调用countDown来使计数器减一。
执行减一操作后,线程不一定要终止,也可以继续执行任务(如上图Ta,Td)。

Demo

public class UseCountDownLatch {
	//计数器设置为6
	static CountDownLatch latch=new CountDownLatch(6);
	private static class InitThread implements Runnable{

		@Override
		public void run() {
			System.out.println("Thread_"+Thread.currentThread().getId()+
				" ready init work...");
				//计数器减1
			latch.countDown();
			for(int i=0;i<2;i++){
				System.out.println("Thread_"+Thread.currentThread().getId()
					+"......continue do its work");
			}
		}
	}

	private static class BusiThread implements Runnable{

		@Override
		public void run() {
			try {
				//在此处会阻塞,当计数器扣减为0时会被唤醒
				latch.await();
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
			for(int i=0;i<3;i++){
				System.out.println("BusiThread_"+Thread.currentThread().getId()
				+" do business----");
			}
		}
	}

	public static void main(String[] args) {
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					TimeUnit.MILLISECONDS.sleep(1);
					System.out.println("Thread_"+Thread.currentThread().getId()+
						" ready init work step 1st...");
					latch.countDown();
					System.out.println("begin step 2nd....");
					TimeUnit.MILLISECONDS.sleep(1);
					System.out.println("Thread_"+Thread.currentThread().getId()
						+" ready init work step 2nd...");
					latch.countDown();
				} catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
			}
		}).start();
		new Thread(new BusiThread()).start();
		for(int i=0;i<=3;i++){
			new Thread(new InitThread()).start();
		}
		try {
			latch.await();
			System.out.println("Main do ites work ...");
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
	}
}

运行结果:
在这里插入图片描述

三、CyclicBarrier

CyclicBarrier可以实现让一组线程达到一个屏障(Barrier)时被阻塞,当所有线程都到达屏障时,被阻塞的线程才会继续执行
Demo

public class UseCyclicBarrier {

	//屏障拦截四个线程,当屏障放开时,会执行传入的CollectThread
	private static CyclicBarrier barrier=new CyclicBarrier(4,new CollectThread());
	//存储子线程的工作结果
	private static ConcurrentHashMap<String,Long> resultmap
		=new ConcurrentHashMap<>();

	public static void main(String[] args) {
		for(int i=0;i<=3;i++){
			new Thread(new SubThread()).start();
		}
	}
	private static class CollectThread implements Runnable{

		@Override
		public void run() {
			StringBuilder res=new StringBuilder();
			for(Map.Entry<String,Long> r:resultmap.entrySet()){
				res.append("["+r.getValue()+"]");
			}
			System.out.println("the result ="+res);
		}
	}

	private static class SubThread implements Runnable{
		@Override
		public void run() {
			long id=Thread.currentThread().getId();
			resultmap.put(Thread.currentThread().getId()+"",id);
			System.out.println("Thread_"+id+"...do something");
			try {
				//在此处被屏障拦截,当屏障放开后才会继续执行
				barrier.await();
				System.out.println("Thread_"+id+"...do its business");
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			} catch (BrokenBarrierException e) {
				throw new RuntimeException(e);
			}
		}
	}

}

结果:
在这里插入图片描述

四、Semaphore

Semaphore的中文翻译就是信号量,是用来进行流量控制的,可以协调各个线程合理的使用资源。
new Semaphore(10) 来创建一个信号量,值为10,这里会创建一个非公平的锁的同步阻塞队列。
acquire方法信号量-1 release方法信号量+1 信号量为0时再执行acquire就会阻塞,直到信号量不为0时(其他线程执行了release)才会继续运行

1.Semaphore实现连接池

注意,实现连接池时需要用两个Semaphore,因为通过release归还时,信号量会超出10个的限制

public class DBPoolSemaphore {
	private final static int POOL_SIZE=10;
	//可用连接和已用连接
	private final Semaphore useful,useless;
	//存放数据库连接的容器
	private static LinkedList<Connection> pool=new LinkedList<>();

	public DBPoolSemaphore() {
		this.useful=new Semaphore(10);
		this.useless=new Semaphore(0);
		for(int i=0;i<POOL_SIZE;i++){
			pool.addLast(SqlConnectImpl.fetchConnection());
		}
	}
	//归还连接
	public void returnConnect(Connection connection) throws InterruptedException {
		if(connection!=null){
			System.out.println("There are now"+useful.getQueueLength()+"threads waiting to connection "+
				"useful connection:"+ useful.availablePermits());
			useless.acquire();
			synchronized (pool){
				pool.addLast(connection);
			}
			useful.release();
		}
	}
	//获取连接
	public Connection getConnect() throws InterruptedException {
		useful.acquire();
		Connection connection;
		synchronized (pool){
			connection=pool.removeFirst();
		}
		useless.release();
		return connection;
	}
}

public class AppTest {
	private static DBPoolSemaphore dbPool=new DBPoolSemaphore();
	private static class BusiThread extends Thread{
		@Override
		public void run() {
			Random r=new Random();
			long start=System.currentTimeMillis();
			try {
				Connection connection= dbPool.getConnect();
				System.out.println("Thread_"+Thread.currentThread().getId()+
					"get db connection use time:"+(System.currentTimeMillis()-start)+"ms");
				TimeUnit.MILLISECONDS.sleep(100+r.nextInt(100)); //模拟业务操作
				System.out.println("task completion,return connection");
				dbPool.returnConnect(connection);
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
		}
	}

	public static void main(String[] args) {
		for(int i=0;i<50;i++){
			Thread thread=new BusiThread();
			thread.start();
		}
	}
}

运行结果:
在这里插入图片描述
在这里插入图片描述

2.思考

使用双信号量是为了防止信号量会超过10个的限制,如果按如下的方法调用连接池:

public class AppTest {
	private static DBPoolSemaphore dbPool=new DBPoolSemaphore();
	private static class BusiThread extends Thread{
		@Override
		public void run() {
			Random r=new Random();
			long start=System.currentTimeMillis();
			try {
//				Connection connection= dbPool.getConnect();
//				System.out.println("Thread_"+Thread.currentThread().getId()+
//					"get db connection use time:"+(System.currentTimeMillis()-start)+"ms");
//				TimeUnit.MILLISECONDS.sleep(100+r.nextInt(100)); //模拟业务操作
//				System.out.println("task completion,return connection");
				dbPool.returnConnect(new SqlConnectImpl());
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
		}
	}

	public static void main(String[] args) {
		for(int i=0;i<50;i++){
			Thread thread=new BusiThread();
			thread.start();
		}
	}
}

在线程中,只归还连接,归还的是自己new出来的连接。如果此时是单信号量只有useful,那么useful会变成60个:
在这里插入图片描述

    //单信号量
	public void returnConnect(Connection connection) throws InterruptedException {
		if(connection!=null) {
			System.out.println("There are now"+useful.getQueueLength()+"threads waiting to connection "+
				"useful connection:"+ useful.availablePermits());
			synchronized (pool) {
				pool.addLast(connection);
			}
			useful.release();
		}
	}

如果采用两个信号量,因为useless一开始为0,所以没有get连接直接归还连接时,会在useless.acquire那里阻塞住,可以有效的防止上面情况的发生。

	//双信号量
	public void returnConnect(Connection connection) throws InterruptedException {
		if(connection!=null){
			System.out.println("There are now"+useful.getQueueLength()+"threads waiting to connection "+
				"useful connection:"+ useful.availablePermits());
			System.out.println("1");
			useless.acquire(); //useless一开始为0.直接调用returnConnect会在这里阻塞住
			System.out.println("2");
			synchronized (pool){
				pool.addLast(connection);
			}
			useful.release();
		}
	}

在这里插入图片描述
log中并没有2,归还连接时被阻塞在useless.acquire

总之,双信号量可以有效的防止可用连接溢出的情况发生。个人感觉,如果是实现一个线程池,线程池中的连接不能让用户通过new SqlConnectImpl()这种形式new出来,SqlConnectImpl应该是对用户不可见的。对于用户来说,应该只能通过getConnect来从线程池获取连接,这样或许也能够避免这种问题出现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/265395.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

官宣!DevExpress Blazor UI组件,支持全新的.NET 8渲染模式

DevExpress Blazor UI组件使用了C#为Blazor Server和Blazor WebAssembly创建高影响力的用户体验&#xff0c;这个UI自建库提供了一套全面的原生Blazor UI组件&#xff08;包括Pivot Grid、调度程序、图表、数据编辑器和报表等&#xff09;。 .NET 8为Blazor引入了令人兴奋的重…

柯桥外语学习-俄语零基础入门教学之与衣服有关的词汇

本期为大家带来的是与衣物有关的相关词汇&#xff01; 最近全国大范围降温&#xff0c;大家一定要关注天气预告及时增减衣物&#xff0c;小心不要感冒啦~ 一、服装组成部分 领子 воротник 方领 квадрадный воротник 圆领 закругленн…

基于SSM框架的电脑测评系统论文

基于 SSM框架的电脑测评系统 摘要 随着信息技术在管理上越来越深入而广泛的应用&#xff0c;作为一个一般的用户都开始注重与自己的信息展示平台&#xff0c;实现基于SSM框架的电脑测评系统在技术上已成熟。本文介绍了基于SSM框架的电脑测评系统的开发全过程。通过分析用户对于…

Wavesurfer.js绘制波形图

HTML使用Wavesurfer.js 要使用wavesurfer.js&#xff0c;首先需要在HTML文件中引入Wavesurfer.js库&#xff0c;然后创建一个音频元素并将其添加到页面中。接下来&#xff0c;初始化Wavesurfer实例并配置相关选项。以下是一个简单的示例&#xff1a; 在HTML文件中引入Wavesurf…

瑞幸咖啡用户运营的秘诀是什么?普通用户通过数据分析也能得到答案!

大数据产业创新服务媒体 ——聚焦数据 改变商业 在快速发展的数字经济时代&#xff0c;BI已成为企业决策过程中不可或缺的工具。通过高效地收集、处理和分析海量数据&#xff0c;BI技术赋予企业洞察市场动态、优化运营策略、提升客户体验的能力。与人工智能、大数据和云计算的…

碳排放预测 | 基于ARIMA和GM(1,1)的碳排放预测(Matlab)

目录 预测效果基本介绍模型描述ARIMA模型GM(1,1)模型 程序设计参考资料 预测效果 基本介绍 基于ARIMA和GM(1,1)的碳排放预测&#xff08;Matlab&#xff09; 基于ARIMA&#xff08;自回归移动平均模型&#xff09;和GM(1,1)&#xff08;灰色预测模型&#xff09;的碳排放预测是…

如何自定义右键弹框并实现位置自适应?

一、问题 右键显示弹框&#xff0c;但是靠近浏览器边缘的部分会被隐藏&#xff0c;需要实现弹框位置自适应 二、 问题分析 如果想要最终弹框的宽高不超过屏幕视口&#xff0c;就等于屏幕视口的总宽/高减去弹框打开时的起点坐标&#xff0c;剩下的部分大于等于弹框的宽/高&…

智能优化算法应用:基于猎食者算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于猎食者算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于猎食者算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.猎食者算法4.实验参数设定5.算法结果6.参考文…

flutter开发实战-设置bottomNavigationBar中间按钮悬浮效果

flutter开发实战-设置bottomNavigationBar中间按钮悬浮的效果 在使用tabbar时候&#xff0c;可以使用bottomNavigationBar来设置中间凸起的按钮&#xff0c;如下 一、效果图 中间按钮凸起的效果图如下 二、实现代码 我们使用BottomAppBar 一个容器&#xff0c;通常与[Sscaf…

【Redis】六、Redis发布订阅

文章目录 1、订阅/发布消息图&#xff1a;2、Redis命令3、测试订阅端:发送端: 3、原理vx公众号发文章 4、使用场景 参考&#xff1a;狂神说Java 在之前的线程通信概念中&#xff0c;也有一个队列&#xff0c;发送者线程把消息发给接收者线程。 Redis 发布订阅(pub/sub)是一种消…

后台留言列表

<!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>后台管理系统</title> <meta http-equiv"X-UA-Compatible" content"IEedge,chrome1"> <meta http-equiv"Acc…

步兵 cocos2dx 加密和混淆

文章目录 摘要引言正文代码加密具体步骤代码加密具体步骤测试和配置阶段IPA 重签名操作步骤 总结参考资料 摘要 本篇博客介绍了针对 iOS 应用中的 Lua 代码进行加密和混淆的相关技术。通过对 Lua 代码进行加密处理&#xff0c;可以确保应用代码的安全性&#xff0c;同时提高性…

java设计模式学习之【命令模式】

文章目录 引言命令模式简介定义与用途实现方式 使用场景优势与劣势在Spring框架中的应用股票示例代码地址 引言 想象一下&#xff0c;你在一个忙碌的厨房里&#xff0c;厨师们正忙于准备各种菜肴。每当服务员带来一个新订单时&#xff0c;他们不会直接对厨师说需要做什么菜。相…

Elasticsearch Reroute API 的使用

本文通过一个 Elasticsearch 集群中主分片分配不均衡的例子演示一下 Cluster reroute API 的使用。 对于 Elasticsearch 分片分配策略不了解的同学可以点一下关注&#xff0c;后面更文之后获取第一手资料。 环境信息 Windows 10 Elasticsearch 8.1 JDK17 初始集群状态 分片…

在Jetpack Compose中使用ExoPlayer进行直播流和音频均衡器

在Jetpack Compose中使用ExoPlayer进行直播流和音频均衡器 背景 ExoPlayer与Media3的能力结合&#xff0c;为Android应用程序播放多媒体内容提供了强大的解决方案。在本教程中&#xff0c;我们将介绍如何设置带有Media3的ExoPlayer来支持使用M3U8 URL进行直播流。此外&#x…

17个常用经典数据可视化图表与冷门图表

数据可视化是创建信息图形表示的过程。随着可视化技术的飞速发展&#xff0c;可以利用强大的可视化工具选择合适的数据可视化图表来展示数据。以下专业人士都应该知道的一些最重要的数据可视化图表。 常见数据可视化图表 饼图 饼图是最常见和最基本的数据可视化图表之一。饼图…

C++ 复合数据类型:指针

文章目录 指针用法无效指针空指针void *ptr指向指针的指针指向常量的指针指针常量指针和数组指针数组和数组指针 指针 计算机中的数据都存在内存中&#xff0c;访问内存的最小单元是“字节”&#xff0c;所有数据&#xff0c;就保存在内存中具有连续编号的一串字节中。 指针顾…

python三大开发框架django、 flask 和 fastapi 对比

本文讲述了什么启发了 FastAPI 的诞生&#xff0c;它与其他替代框架的对比&#xff0c;以及从中汲取的经验。 如果不是基于前人的成果&#xff0c;FastAPI 将不会存在。在 FastAPI 之前&#xff0c;前人已经创建了许多工具 。 几年来&#xff0c;我一直在避免创建新框架。首先&…

竞赛保研 基于LSTM的天气预测 - 时间序列预测

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 机器学习大数据分析项目 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f9ff; 更多资料, 项目分享&#xff1a; https://gitee.com/dancheng-senior/po…

Python深度学习029:pytorch中常用的模块或方法

PyTorch是一个广泛使用的深度学习库,提供了许多用于构建和训练神经网络的模块和方法。下面是一些PyTorch中常用的模块和方法的简要介绍: PyTorch常用模块和方法 torch 用途:PyTorch的基础模块,提供了多种数学运算功能。常用方法: torch.tensor():创建张量torch.randn():…