一 环境初始化
- 现在码云上建立新仓库 Concurrency ,我的Concurrency
- 通过 https://start.spring.io/ 新建 spring boot 项目 concurrency
- 在本地Git仓库把 码云项目拉下来
git clone https://gitee.com/libinliu/Concurrency.git
- 将 步骤2中的项目信息复制到 本地仓库中
- git push 到远程仓库
二 案例准备
自定义部分注解来描述我们后面写的类的一些特性:
ThreadSafe 注解表示线程安全的类或者写法
NotThreadSafe 注解表示线程不安全的类或者写法
Recommend 注解表示推荐的类或者写法
NotRecommend 注解表示不推荐的类或者写法
ThreadSafe类示例
package com.hust.concurrency.annoations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Description 标记 [线程安全] 的类或者写法
* @since 2019年1月8日 下午2:29:03
* @author LiuLiBin
*/
//@Target 表示这个注解能放在什么位置上,是只能放在类上?还是即可以放在方法上,又可以放在属性上
//ElementType.TYPE :表示 能修饰类、接口或枚举类型
@Target(ElementType.TYPE)
//@Retention 表示生命周期,
// RetentionPolicy.SOURCE: 注解只在源代码中存在,编译成class之后,就没了。@Override 就是这种注解
@Retention(RetentionPolicy.SOURCE)
public @interface ThreadSafe {
/* 成员变量
* 1. 注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型
*
* 2. 注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组
*
* 3. 如果注解只有一个成员,则成员名必须取名为 value() ,在使用时可以忽略成员名和赋值号(=)
*
* 4. 注解类可以没有成员,没有成员的注解称为标识注解
* */
String value() default "";
}
三 并发模拟-工具
1. postman 并发测试步骤
- 新开接口 localhost:8080/test,在postman中测试接口访问通过后,将接口保存在左侧的Collections中
- 开始并发测试
2. Apache Bench 并发模拟的性能测试
ApacheBench 是 Apache 服务器自带的一个web压力测试工具,简称ab。ab又是一个命令行工具,对发起负载的本机要求很低,根据ab命令可以创建很多的并发访问线程,模拟多个访问者同时对某一URL地址进行访问,因此可以用来测试目标服务器的负载压力。总的来说ab工具小巧简单,上手学习较快,可以提供需要的基本性能指标,但是没有图形化结果,不能监控。
-
ab的用法1
- 首先,我们输入cmd打开DOS窗口,然后将更改当前工作目录为:Apache安装目录/bin/。然后键入帮助命令ab -help(或者ab /?、ab -h),我们就可以看到用法介绍界面。
- ab可以配置的参数选项比较多,但是,一般情况下我们只需要使用形如ab -n 数字 -c 数字 url路径的命令即可。譬如,我们对位于本地Apache服务器上、URL为localhost/index.php的页面进行压力测试。测试总次数为1000,并发数为100(相当于100个用户同时访问,他们总共访问1000次)。我们输入DOS命令ab -n 1000 -c 100 localhost/index.php
Server Software: Apache/2.2.25 (服务器软件名称及版本信息) Server Hostname: localhost (服务器主机名) Server Port: 80 (服务器端口) Document Path: /index.php (供测试的URL路径) Document Length: 10 bytes (供测试的URL返回的文档大小) Concurrency Level: 100 (并发数) Time taken for tests: 0.247 seconds (压力测试消耗的总时间) Complete requests: 1000 (压力测试的总次数) Failed requests: 0 (失败的请求数) Write errors: 0 (网络连接写入错误数) Total transferred: 198000 bytes (传输的总数据量) HTML transferred: 10000 bytes (HTML文档的总数据量) Requests per second: 4048.34 [#/sec] (mean) (平均每秒的请求数) Time per request: 24.701 [ms] (mean) (所有并发用户(这里是100)都请求一次的平均时间) Time per request: 0.247 [ms] (mean, across all concurrent requests) (单个用户请求一次的平均时间) Transfer rate: 782.78 [Kbytes/sec] received (传输速率,单位:KB/s)
3. JMeter 并发模拟的性能测试
- JMeter 简单使用
添加线程组
填写线程组信息
添加Http请求
完善 Http请求
添加监听器监视请求结果
运行请求并分析结果
四 并发模拟-代码
使用 CountDownLatch和 Semaphore 来模拟并发2模型3。
ConcurrencyTest:
package com.hust.concurrency.service;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import lombok.extern.slf4j.Slf4j;
/**
* @Description 使用 [CountDownLatch]和 [Semaphore] 和 [ExecutorService] 来模拟并发状态。
* @since 2019年1月8日 下午8:37:54
* @author LiuLiBin
*/
@Slf4j
public class ConcurrencyTest {
//请求总数
public static int clientTotal = 1000;
//同时并发执行的线程数
public static int threadTotal = 50;
//测试并发 的计数值
public static int count = 0 ;
public static void main(String[] args) throws InterruptedException {
//定义线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//定义信号量 ,信号量为 50
final Semaphore semaphore = new Semaphore(threadTotal);
//定义计数器闭锁 ,期望所有请求请求完后统计计数结果
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
//放入请求,将请求线程全部放入线程池中
for (int i = 0; i < clientTotal; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
try {
semaphore.acquire(); // 获取一个许可
add();
semaphore.release(); // 释放一个许可
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown(); // 计数器闭锁减1
}
});
}
//在所有线程请求执行完后 ,唤醒等待线程 ,此处涉及到countDownLatch 的实现机制
countDownLatch.await();
//关闭线程池
executorService.shutdown();
//打印计数值信息
log.info("count:{}",count);
}
/**
* 增加计数值
*/
private static void add() {
count++;
}
}
运行上面代码会发现,计数值不是固定不变的