Java基于线程池和AQS模拟高并发
概述
《手写高并发下线程安全的单例模式》主要介绍使用枚举类实现JAVA单例模式,以及在高并发环境下验证此单例模式是线程安全的。本文借助ReentrantLock、CountDownLatch和Semaphore等,基于线程池验证如何创建线程安全和不安全的方法。
实际项目中,我们有很多高并发的场景需要考虑、设计,在高并发领域有个耳熟能详的名词叫惊群效应。以喂鸽子为例进行说明,当你往一群鸽子中间扔一块食物时,虽然最终只有一只鸽子抢到食物,但所有鸽子都会被惊动从而飞过来争夺,没有抢到食物的鸽子只好回去继续睡觉, 等待下一块食物到来。这样,每扔一块食物,都会惊动所有的鸽子,即为惊群。也就是说,虽然只扔了一块食物,但是惊动了整个鸽群,最后却只有一只鸽子抢到食物,浪费了其它鸽子的能量。
对于操作系统来说,多个进程/线程在等待同一资源时,也会产生类似的效果,其结果就是每当有可用资源时,所有的进程/线程都来竞争资源,造成了资源的浪费[2]:
- 系统对用户进程/线程频繁做无效的调度和上下文切换,导致系统性能大打折扣。
- 为了确保只有一个线程得到资源,用户必须对资源操作进行加锁保护,进一步加大了系统开销。
下面基于ReentrantLock、CountDownLatch和Semaphore创建一个并发模拟工具,当被调用函数不出现惊群效应时,说明是线程安全的。
CountDownLatch是一个能阻塞主线程,让其它线程满足特定条件下再继续执行的工具。比如倒计时3000,每当一个线程完成一次操作就让它执行countDown一次,直到count为0之后输出结果,这样就保证了其它线程一定是满足了特定条件(执行某操作5000次),模拟了并发执行次数。
Semaphore信号量是一个能阻塞线程且能控制统一时间请求的并发量的工具。比如能保证同时执行的线程最多200个,模拟出稳定的并发量。每个acquire方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个release方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可证这个对象,Semaphore只是维持了一个可获得许可证的数量。
模拟工具
创建模拟工具,首先创建一个线程安全地售票的任务类:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
/**
* 线程安全地售票
*/
public class SafeSale implements Runnable {
//定义车票的数量
private int ticket = 100;
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
boolean a = true;
while (a) {
try {
//对操作共享数据的代码进行加锁
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":" + "出售第" + ticket + "张车票");
//车票数量减一
ticket--;
//线程休眠,增加其他线程调用的机会
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
a = false;
}
} finally {
lock.unlock(); //进行解锁
}
}
}
}
紧接着,创建测试用例所需要的非线程安全函数和main函数:
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
/**
* 模拟高并发
*/
public class ConcurrencyCheck {
// 执行次数
private static final int THREAD_COUNT = 3000;
// 并发数
private static final int CONCURRENT_COUNT = 200;
// 全局变量,容易出幺蛾子
private static int count = 10000;
public static void main(String[] args) throws InterruptedException {
lockSale();
checkUtil();
}
/**
* 基于lock方法售票
*/
private static void lockSale() {
ExecutorService executorService = Executors.newCachedThreadPool();
SafeSale sale = new SafeSale();
for (int i = 0; i < THREAD_COUNT; i++) {
executorService.execute(sale);
}
executorService.shutdown();
}
/**
* 模拟线程非安全,模拟工具主题
* @throws InterruptedException
*/
private static void checkUtil() throws InterruptedException {
ExecutorService executorService = Executors.newCachedThreadPool();
Semaphore semaphore = new Semaphore(CONCURRENT_COUNT);
CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT);
for (int i = 0; i < THREAD_COUNT; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
subtraction();
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
System.out.println("计数结果:" + count);
}
/**
* 售票,非线程安全,被验证对象
*/
private static void subtraction() {
count--;
}
}
如果多运行几次如上main函数就会发现,方法subtraction()并不是线程安全的,即其执行结果几乎都是大于7000,很少等于7000。
Reference
[1] https://www.jianshu.com/p/2da329ba5349
[2] https://blog.csdn.net/shipfei_csdn/article/details/103110621
Java基于线程池和AQS模拟高并发的更多相关文章
- springboot2.0+线程池+Jmeter以模拟高并发
声明:原创在这里https://blog.csdn.net/u011677147/article/details/80271174,在此也谢谢哥们. 1.目录结构 2.BusinessThread.j ...
- 【Java TCP/IP Socket】基于线程池的TCP服务器(含代码)
了解线程池 在http://blog.csdn.net/ns_code/article/details/14105457(读书笔记一:TCP Socket)这篇博文中,服务器端采用的实现方式是:一个客 ...
- Java中线程池的学习
线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理.当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程 ...
- 基于线程池的多线程售票demo2.0(原创)
继上回基于线程池的多线程售票demo,具体链接: http://www.cnblogs.com/xifenglou/p/8807323.html以上算是单机版的实现,特别使用了redis 实现分布式锁 ...
- 基于线程池的多线程售票demo(原创)
废话不多说,直接就开撸import org.springframework.util.StopWatch;import java.util.concurrent.*;/** * 基于线程池实现的多线程 ...
- 深入理解Java之线程池
原作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本文归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则 ...
- 设计模式:基于线程池的并发Visitor模式
1.前言 第二篇设计模式的文章我们谈谈Visitor模式. 当然,不是简单的列个的demo,我们以电商网站中的购物车功能为背景,使用线程池实现并发的Visitor模式,并聊聊其中的几个关键点. 一,基 ...
- java利用线程池处理集合
java利用线程池处理集合 2018年07月23日 17:21:19 衍夏成歌 阅读数:866 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/s ...
- 深入理解Java之线程池(爱奇艺面试)
爱奇艺的面试官问 (1) 线程池是如何关闭的 (2) 如何确定线程池的数量 一.线程池销毁,停止线程池 ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown() ...
- Java中线程池,你真的会用吗?
在<深入源码分析Java线程池的实现原理>这篇文章中,我们介绍过了Java中线程池的常见用法以及基本原理. 在文中有这样一段描述: 可以通过Executors静态工厂构建线程池,但一般不建 ...
随机推荐
- Vue3 路由配置与导航全攻略:从零到精通
在现代前端开发中,单页应用(SPA)已经成为主流趋势.而作为 Vue.js 的核心功能之一,Vue Router 提供了强大的路由管理能力,帮助开发者轻松构建流畅.高效的单页应用.本文将带你深入探讨 ...
- angular+ionic项目,页面无法滚动的问题
在做angular+ionic+cordova项目时,遇到一个小小的问题,就是内容做完,页面无法滚动,导致内容显示不完整 首先我检查了样式,发现并没有给页面定死高度,再次检查结构发现,我并没有用ion ...
- Vite项目入口文件
官方文档:https://cn.vitejs.dev/guide/#index-html-and-project-root
- 本地如何访问vue2 生成的dist代码
前言 当你使用 Vue CLI 或其他构建工具构建 Vue 2 项目时,它会生成一个 dist 文件夹,这个文件夹包含了你项目的生产环境版本的静态资源文件(HTML.JavaScript 和 CSS) ...
- Huawei Cloud EulerOS上安装sshpass
下载源码 git clone https://github.com/kevinburke/sshpass.git 由于网络问题,这里我用了一个代理下载 git clone https://ghprox ...
- php7安装redis6扩展
1.下载 php-redis扩展下载地址: http://pecl.php.net/package/redis 具体下载版本以自己的PHP版本信息为准 linux下载命令 wget http://pe ...
- SQL Server 中的异常处理
为什么我们需要 SQL Server 中的异常处理? 让我们通过一个示例来了解 SQL Server 中异常处理的必要性.因此,创建一个 SQL Server 存储过程,通过执行以下查询来除以两个数字 ...
- Spring Cloud Gateway限流极速部署:3步搞定,秒级防护微服务!
Spring Cloud Gateway限流极速部署:3步搞定,秒级防护微服务! 想要快速为Spring Cloud Gateway集成限流功能?本文提供最简方案,无需复杂配置,三步即可完成!通过内置 ...
- Netty源码—6.ByteBuf原理一
大纲 1.关于ByteBuf的问题整理 2.ByteBuf结构以及重要API 3.ByteBuf的分类 4.ByteBuf分类的补充说明 5.ByteBuf的主要内容分三大方面 6.内存分配器Byte ...
- Oracle 11G R2 安装图解
个人学习需要,在Windows Server 2008 R2 上安装 Oracle 11G R2 Tips:需要下载2个文件,file1和file2 解压后需要合并到同一个文件夹下才能正常安装(这里就 ...