概述

  《手写高并发下线程安全的单例模式》主要介绍使用枚举类实现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模拟高并发的更多相关文章

  1. springboot2.0+线程池+Jmeter以模拟高并发

    声明:原创在这里https://blog.csdn.net/u011677147/article/details/80271174,在此也谢谢哥们. 1.目录结构 2.BusinessThread.j ...

  2. 【Java TCP/IP Socket】基于线程池的TCP服务器(含代码)

    了解线程池 在http://blog.csdn.net/ns_code/article/details/14105457(读书笔记一:TCP Socket)这篇博文中,服务器端采用的实现方式是:一个客 ...

  3. Java中线程池的学习

    线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理.当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程 ...

  4. 基于线程池的多线程售票demo2.0(原创)

    继上回基于线程池的多线程售票demo,具体链接: http://www.cnblogs.com/xifenglou/p/8807323.html以上算是单机版的实现,特别使用了redis 实现分布式锁 ...

  5. 基于线程池的多线程售票demo(原创)

    废话不多说,直接就开撸import org.springframework.util.StopWatch;import java.util.concurrent.*;/** * 基于线程池实现的多线程 ...

  6. 深入理解Java之线程池

    原作者:海子 出处:http://www.cnblogs.com/dolphin0520/ 本文归作者海子和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则 ...

  7. 设计模式:基于线程池的并发Visitor模式

    1.前言 第二篇设计模式的文章我们谈谈Visitor模式. 当然,不是简单的列个的demo,我们以电商网站中的购物车功能为背景,使用线程池实现并发的Visitor模式,并聊聊其中的几个关键点. 一,基 ...

  8. java利用线程池处理集合

    java利用线程池处理集合 2018年07月23日 17:21:19 衍夏成歌 阅读数:866   版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/s ...

  9. 深入理解Java之线程池(爱奇艺面试)

    爱奇艺的面试官问 (1) 线程池是如何关闭的 (2) 如何确定线程池的数量 一.线程池销毁,停止线程池 ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown() ...

  10. Java中线程池,你真的会用吗?

    在<深入源码分析Java线程池的实现原理>这篇文章中,我们介绍过了Java中线程池的常见用法以及基本原理. 在文中有这样一段描述: 可以通过Executors静态工厂构建线程池,但一般不建 ...

随机推荐

  1. TSP问题的不可近似性

    \(\S\) 结论 TSP问题:n阶带权无向完全图中,找权值最小的哈密顿回路(无向图中遍历所有顶点的回路) 优化问题,记最优解为OPT 对于一般的n顶点TSP问题(非Metric),任意 多项式时间内 ...

  2. 大模型评测之幻觉检测hallucination_evaluation_model

    大背景: 2025开年deepseek铺天盖地的新闻 参会代表已经表明,年度主线就是以AI为基础 Manus于3月初横空出世 国内各种模型竞赛的现状,只要是和科技沾边的公司不可能没有大模型,哪怕是里三 ...

  3. Mysql join算法深入浅出

    导语 联表查询在日常的数据库设计中非常的常见,但是联表查询可能会带来性能问题,为了调优.避免设计出有性能问题的SQL,在explain命令中,会显示用的是哪个join算法,学习一下join过程是非常有 ...

  4. goframe API 自定义接口返回值处理

    前言 goframe 默认使用了中间键 ghttp.MiddlewareHandlerResponse, HTTP Server 的数据返回通过 ghttp.Response 对象实现,ghttp.R ...

  5. Kubernetes的工作机制

    云计算时代的操作系统 Kubernetes 是一个生产级别的容器编排平台和集群管理系统,能够创建.调度容器,监控.管理服务器. Kubernetes 的基本架构 操作系统的一个重要功能就是抽象,从繁琐 ...

  6. CENTOS 7 使用Docker安装oracle 11g

    1.docker第一次安装失败,下一次会提示同名的容器已经存在,解决办法: docker container ls -a 根据上面执行的接口,选择一个containerid,然后带入到下面的命令 do ...

  7. 实现领域驱动设计 - 使用ABP框架 - 系列文章汇总

    系列文章汇总 前言: 最近看到ABP官网的一本电子书,感觉写的很好,翻译出来,一起学习下 Implementing Domain Driven Design 实现领域驱动设计 - 使用ABP框架 - ...

  8. DP——从入门到放弃 [Did Not Finish]

    Part 00 dp 什么时候用? 首先,如果这题数据范围一眼 BFS/DFS/暴力/随机化 可以过,那么还写 dp 干什么 但是,但你决定这题要贪心时,我建议咱还是要看一下它对不对 整一个石子合并这 ...

  9. Random和猜数字小游戏

    1.Random:使用方式和Scanner一样 Random用于生成随机数,括号里的10就是指在10以内随机生成一个数(0~9) Random生成的随机数都是从0开头 . 提问:那该如何让Random ...

  10. Launchpool名词解释

    # 一.什么是Launchpool Launchpool是一种加密货币领域的创新机制,通常由交易所或DeFi平台提供,允许用户通过质押(staking)或锁定特定代币来获得新项目的代币奖励. ## L ...