Java通过代码模拟高并发可以以最快的方式发现我们系统中潜在的线程安全性问题,此处使用Semaphore(信号量)和 CountDownLatch(闭锁)搭配ExecutorService(线程池)来进行模拟,主要介绍如下:
1、Semaphore
JDK 1.5之后会提供这个类
Semaphore是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore可以用来构建一些对象池,资源池之类的,比如数据库连接池,我们也可以创建计数为1的Semaphore,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。
2、CountDownLatch
JDK 1.5之后会提供这个类,
CountDownLatch这个类能够使一个线程等待其他线程完成各自的工作后再执行。例如,应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
如下图:
以上两个类可以搭配使用,达到模拟高并发的效果,以下使用代码的形式进行举例:
- package modules;
- import java.util.concurrent.CountDownLatch;
- import java.util.concurrent.ExecutorService;
- import java.util.concurrent.Executors;
- import java.util.concurrent.Semaphore;
- public class CountExample {
- // 请求总数
- public static int clientTotal = 5000;
- // 同时并发执行的线程数
- public static int threadTotal = 200;
- public static int count = 0;
- public static void main(String[] args) throws Exception {
- ExecutorService executorService = Executors.newCachedThreadPool();
- //信号量,此处用于控制并发的线程数
- final Semaphore semaphore = new Semaphore(threadTotal);
- //闭锁,可实现计数器递减
- final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
- for (int i = 0; i < clientTotal ; i++) {
- executorService.execute(() -> {
- try {
- //执行此方法用于获取执行许可,当总计未释放的许可数不超过200时,
- //允许通行,否则线程阻塞等待,直到获取到许可。
- semaphore.acquire();
- add();
- //释放许可
- semaphore.release();
- } catch (Exception e) {
- //log.error("exception", e);
- e.printStackTrace();
- }
- //闭锁减一
- countDownLatch.countDown();
- });
- }
- countDownLatch.await();//线程阻塞,直到闭锁值为0时,阻塞才释放,继续往下执行
- executorService.shutdown();
- log.info("count:{}", count);
- }
- private static void add() {
- count++;
- }
- }
如上方法模拟5000次请求,同时最大200个并发操作,观察最后的结果,发现每次的结果都有差别,和预期不符,得出结果部分如下:
- 22:18:26.449 [main] INFO modules.CountExample - count:4997
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:4995
- 22:18:26.449 [main] INFO modules.CountExample - count:4998
最后结论:add 方法 非线程安全
那如何保证add方法 线程安全,将add方法进行如下修改即可:
- private static void add() {
- count.incrementAndGet();
- }
执行结果如下:
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
- 22:18:26.449 [main] INFO modules.CountExample - count:5000
最后结论:修改后 的 add 方法 线程安全