在gradle工程使用jmh并集成SpringBoot(1)

文章对比了使用`String`,`StringBuilder`,`StringBuffer`,和并发工具如`Random`与`ThreadLocalRandom`在不同场景下的字符串拼接性能。实验结果显示StringBuilder和StringBuffer比直接字符串连接更高效,而ThreadLocalRandom在生成随机数时表现出色。此外,还涉及到了SpringBoot集成下的同步队列性能测试。
摘要由CSDN通过智能技术生成

return a;

}

@Benchmark

public String testConcat() {

String a = “”;

for (int i = 0; i < size; i++) {

a = a.concat(“” + i);

}

return a;

}

@Benchmark

public String testBuilder() {

StringBuilder a = new StringBuilder();

for (int i = 0; i < size; i++) {

a.append(i);

}

return a.toString();

}

@Benchmark

public String testBuffer() {

StringBuffer a = new StringBuffer();

for (int i = 0; i < size; i++) {

a.append(i);

}

return a.toString();

}

}

结果

Benchmark (size) Mode Cnt Score Error Units

StringTest.testAdd 10 avgt 5 127.439 ± 3.774 ns/op

StringTest.testAdd 100 avgt 5 1915.880 ± 41.530 ns/op

StringTest.testAdd 1000 avgt 5 199442.392 ± 21708.330 ns/op

StringTest.testBuffer 10 avgt 5 89.245 ± 1.318 ns/op

StringTest.testBuffer 100 avgt 5 1026.880 ± 10.613 ns/op

StringTest.testBuffer 1000 avgt 5 13192.122 ± 174.511 ns/op

StringTest.testBuilder 10 avgt 5 71.272 ± 2.464 ns/op

StringTest.testBuilder 100 avgt 5 870.357 ± 14.289 ns/op

StringTest.testBuilder 1000 avgt 5 11498.802 ± 253.298 ns/op

StringTest.testConcat 10 avgt 5 312.639 ± 10.854 ns/op

StringTest.testConcat 100 avgt 5 3675.755 ± 163.944 ns/op

StringTest.testConcat 1000 avgt 5 204830.905 ± 3241.692 ns/op

从上面的结果中可以看到,随着拼接次数的增加10->100->1000每个方法的效率都会降低。但是使用StringBuilder进行字符串拼接的效率依旧是最高的,其次是StringBuffer,然后是字符串加法,效率最低的concat。

基本参数概念

======================================================================

@BenchmarkMode


标识JMH进行Benchmark时所使用的模式。

  1. Throughput:吞吐量。比如“1秒内可以执行多少次调用”,单位是ops/time

  2. AverageTime:每次调用的平均耗时。比如“每次调用平均耗时xxx毫秒”,单位是time/ops

  3. SampleTime:随机取样,最后输出取样结果的分布。比如“99%的调用在xxx毫秒内,99.99%的调用在xxx毫秒以内”

  4. SingleShotTime:只运行一次,往往同时设置warmup=0,一般用于测试冷启动的性能。

上面的这些模式并不是只能使用某一个,这些模式是可以被组合使用的,比如

@BenchmarkMode({Mode.AverageTime, Mode.SampleTime})

@State


通过State可以指定一个对象的作用范围,jmh通过scope来进行实例化和共享操作。@State可以被继承使用,如果父类定义了该注解,子类则无需定义。由于jmh可以进行多线程测试,所以不同的scope的隔离级别如下:

  1. Scope.Benchmark:全局共享,所有的测试线程共享同一个实例对象。可以用来测试有状态的实例在多线程下的性能。

  2. Scope.Group:同一个线程组内部的线程共享一个实例对象。

  3. Scope.Thread:每个线程获取到都是不一样的实例对象。

在上面字符串拼接性能测试的样例中,我们使用的就是Scope.Benchmark

@OutputTimeunit


输出结果的时间单位,咱们上面用的是纳秒,即TimeUnit.NANOSECONDS

@Warmup


程序预热所需要的一些参数,可以用在类或者方法上。由于JVM存在JIT机制,所以一般前几次的效率都可能会不太理想,所以需要让程序先预热一下再跑。这样可以保证测试结果的准确性。参数如下:

  1. iterations:预热的次数,默认值是org.openjdk.jmh.runner.Defaults#WARMUP_ITERATIONS=5

  2. time:每次预热执行的时长,默认值是org.openjdk.jmh.runner.Defaults#WARMUP_TIME=10秒

  3. timeUnit:上面那个时长对应的单位类型,默认是秒

  4. batchSize:每个操作的基准方法调用次数,默认值是org.openjdk.jmh.runner.Defaults#WARMUP_BATCHSIZE=1。1就代表一次一次的调用,如果是2那就代表2次2次的调用。

@Measurement


这个参数与@Warmup中的参数完全一样,只是@Warmup是用在预热上,预热结果不算入最终的结果中。而@Measurement是指实际测试时的参数。

@Fork


默认值是org.openjdk.jmh.runner.Defaults#MEASUREMENT_FORKS=5,可以手动指定。@Fork

中设置是多少,那jmh执行测试时就会创建多少个独立的进程来进行测试。但是需要注意的是,不管有多少个进程进行测试,他们都是串行的。当fork为0时,表示不需要进行fork。官方解释是这样的:

JVMs are notoriously good at profile-guided optimizations. This is bad for benchmarks, because different tests can mix their profiles together, and then render the “uniformly bad” code for every test. Forking (running in a separate process) each test can help to evade this issue.

JMH will fork the tests by default.

翻译成人话就是说,首先因为JVM存在profile-guided optimizations

的特性,但是这样的特性是不利于进行基准测试的。因为不同的测试方法会混在一起,最后会导致结果出现偏差。为了避免这样的偏差才有了@Fork的存在。关于这个偏差的问题可以参考官方的这个例子:code-tools/jmh: 2be2df7dbaf8 jmh-samples/src/main/java/org/openjdk/jmh/samples/JMHSample_12_Forking.java

所以为了避免这样的问题,我们可以设置@Fork(1)这样每一个测试的方法都会跑在不同的jvm进程中,也就避免了相互影响。

@Thread


每一个测试进程(JVM)中的线程数。

@Param


用来指定某个参数的多种情况,比如上面字符串的例子中的:

@Param({“10”, “100”, “1000”})

private int size;

特别适合用来测试一个函数在不同的参数输入的情况下的性能。只能用在字段上,同时必须使用@State注解声明隔离级别。

实战

==================================================================

一、Random与ThreadLocalRandom


我们都知道获取随机数可以使用Random,同时在官方文档中也强调Random虽然是线程安全的,但是如果在多线程的情况下,最好还是使用ThreadLocalRandom

。那么,Random与ThreadLocalRandom在效率上相差多少呢?我们在实际使用过程中该如何选择呢?

@BenchmarkMode({Mode.AverageTime})

@OutputTimeUnit(TimeUnit.NANOSECONDS)

@Warmup(time = 1, iterations = 3)

@Measurement(time = 1, iterations = 5)

@Fork(1)

@Threads(5)

@State(value = Scope.Benchmark)

public class RandomTest {

private final Random random = new Random();

private final ThreadLocal randomThreadLocalHolder = ThreadLocal.withInitial(Random::new);

public static void main(String[] args) throws RunnerException {

Options opt = new OptionsBuilder()

.include(RandomTest.class.getSimpleName())

.result(“RandomTest_result.json”)

.resultFormat(ResultFormatType.JSON).build();

new Runner(opt).run();

}

@Benchmark

public int random() {

return random.nextInt();

}

@Benchmark

public int randomThreadLocalHolder() {

return randomThreadLocalHolder.get().nextInt();

}

@Benchmark

public int threadLocalRandom() {

return ThreadLocalRandom.current().nextInt();

}

}

看下结果:

Benchmark Mode Cnt Score Error Units

RandomTest.random avgt 5 423.784 ± 20.159ns/op

RandomTest.randomThreadLocalHolder avgt 5 11.369 ± 0.509ns/op

RandomTest.threadLocalRandom avgt 5 4.322 ± 0.374ns/op

从结果上看ThreadLocalRandom.current().nextInt()

完胜,而且效率差别非常大。同时我们也没必要自己搞ThreadLocal来封装Random。因为JDK提供的ThreadLocalRandom.current()就已经是天花板了。

二、写热点


@BenchmarkMode({Mode.AverageTime})

@OutputTimeUnit(TimeUnit.NANOSECONDS)

@Warmup(time = 1, iterations = 3)

@Measurement(time = 1, iterations = 5)

@Fork(1)

@Threads(10)

@State(value = Scope.Benchmark)

public class HotWriteTest {

private final LongAdder longAdder = new LongAdder();

private final AtomicLong atomicLong = new AtomicLong();

public static void main(String[] args) throws RunnerException {

Options opt = new OptionsBuilder()

.include(HotWriteTest.class.getSimpleName())

.result(“HotWriteTest_result.json”)

.resultFormat(ResultFormatType.JSON).build();

new Runner(opt).run();

}

@Benchmark

public void longAdder() {

longAdder.increment();

}

@Benchmark

public void atomicLong() {

atomicLong.incrementAndGet();

}

}

测试结果:

Benchmark Mode Cnt Score Error Units

HotWriteTest.atomicLong avgt 5 210.160 ± 27.965 ns/op

HotWriteTest.longAdder avgt 5 14.293 ± 2.339 ns/op

三、同步队列性能测试+SpringBoot集成


SpringBoot工程如下:

public interface IQueue {

void put(Object o) throws InterruptedException;

Object take() throws InterruptedException;

}

@Component(“arrayQueue”)

public class ArrayQueue implements IQueue {

private static final ArrayBlockingQueue QUEUE = new ArrayBlockingQueue<>(100000);

@Override

public void put(Object o) throws InterruptedException {

QUEUE.put(o);

}

@Override

public Object take() throws InterruptedException {

return QUEUE.take();

}

}

@Component(“linkedQueue”)

public class LinkedQueue implements IQueue {

private static final LinkedBlockingQueue QUEUE = new LinkedBlockingQueue<>(100000);

@Override

public void put(Object o) throws InterruptedException {

QUEUE.put(o);

}

@Override

public Object take() throws InterruptedException {

return QUEUE.take();

}

}

@SpringBootApplication(scanBasePackages = “com.example.jmh”)

public class SpringBootApp {

public static void main(String[] args) {

ConfigurableApplicationContext context = SpringApplication.run(SpringBootApp.class, args);

IQueue arrayQueue = context.getBean(“arrayQueue”, IQueue.class);

IQueue linkedQueue = context.getBean(“linkedQueue”, IQueue.class);

}

}

测试代码如下:

@BenchmarkMode({Mode.AverageTime})

@OutputTimeUnit(TimeUnit.NANOSECONDS)

@Warmup(time = 1, iterations = 2)

@Measurement(time = 1, iterations = 3)

@Fork(1)

@State(Scope.Group)

public class SpringBootTest {

private ConfigurableApplicationContext applicationContext;

private IQueue arrayQueue;

private IQueue linkedQueue;

public static void main(String[] args) throws RunnerException {

Options opt = new OptionsBuilder()

.include(SpringBootTest.class.getSimpleName())

.result(“SpringBootTest.json”)

.resultFormat(ResultFormatType.JSON).build();

new Runner(opt).run();

}

@Setup

public void init() {

applicationContext = SpringApplication.run(SpringBootApp.class);

arrayQueue = applicationContext.getBean(“arrayQueue”, IQueue.class);

linkedQueue = applicationContext.getBean(“linkedQueue”, IQueue.class);

}

@TearDown

public void down() {

applicationContext.close();

}

@Group(“arrayQueue”)

@GroupThreads(2)

@Benchmark

public void arrayQueuePut() throws InterruptedException {

arrayQueue.put(new Object());

}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

无论是哪家公司,都很重视基础,大厂更加重视技术的深度和广度,面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。

针对以上面试技术点,我在这里也做一些分享,希望能更好的帮助到大家。

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!**

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

无论是哪家公司,都很重视基础,大厂更加重视技术的深度和广度,面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。

针对以上面试技术点,我在这里也做一些分享,希望能更好的帮助到大家。

[外链图片转存中…(img-8g6CDoSO-1713600289112)]

[外链图片转存中…(img-28THPED5-1713600289112)]

[外链图片转存中…(img-cCr5jSvL-1713600289112)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 29
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,关于使用 Gradle 创建多模块 Spring Boot 工程的方法,可以按照以下步骤操作: 1. 创建 Gradle 项目 在 IDEA 中创建一个 Gradle 项目,选择 Kotlin 或者 Java 作为编程语言,选择 Gradle 作为项目类型,然后点击下一步。 2. 创建多模块工程 在项目创建完成后,可以在项目根目录下创建一个 settings.gradle 文件,然后在其中定义多模块工程的名称,例如: ``` rootProject.name = 'my-spring-boot-project' include 'my-spring-boot-project-core' include 'my-spring-boot-project-web' ``` 这里我们定义了两个子模块,即 my-spring-boot-project-core 和 my-spring-boot-project-web。 3. 配置子模块 在每个子模块的 build.gradle 文件中,添加 Spring Boot 插件和其他必要的依赖,例如: ``` plugins { id 'org.springframework.boot' version '2.6.0' id 'io.spring.dependency-management' version '1.0.11.RELEASE' kotlin("jvm") version "1.5.31" } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' // 其他依赖 } ``` 4. 配置子模块之间的依赖关系 在每个子模块的 build.gradle 文件中,可以添加对其他子模块的依赖,例如: ``` dependencies { implementation project(':my-spring-boot-project-core') } ``` 这里我们在 my-spring-boot-project-web 子模块中添加了对 my-spring-boot-project-core 子模块的依赖。 5. 运行 Spring Boot 应用 在每个子模块中,都可以创建一个 Application 类来启动 Spring Boot 应用,例如: ``` @SpringBootApplication class MySpringBootProjectApplication fun main(args: Array<String>) { runApplication<MySpringBootProjectApplication>(*args) } ``` 然后在每个子模块的 build.gradle 文件中,添加运行 Spring Boot 应用的任务,例如: ``` tasks.named("bootRun") { dependsOn(":my-spring-boot-project-core:build") classpath = sourceSets.main.get().runtimeClasspath } ``` 这里我们定义了一个 bootRun 任务,它依赖于 my-spring-boot-project-core 子模块的 build 任务,然后设置 classpath。 6. 运行多模块 Spring Boot 应用 最后,在项目根目录下运行以下命令,即可启动多模块 Spring Boot 应用: ``` ./gradlew :my-spring-boot-project-web:bootRun ``` 这里我们启动了 my-spring-boot-project-web 子模块的 Spring Boot 应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值