异步和同步相比能充分的利用好CPU资源。
一、Java语言
1.1 Future
通过继承Thread或者实现Runnable接口可以快速实现多线程,但是这种实现方式只有执行过程没有返回值。为了要让并发任务能返回结果就需要实现Callable和Future来实现。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class FutureTest {
public static void main(String[] args) throws Exception{
ExecutorService executor = Executors.newCachedThreadPool();
// 用lambda函数式来实现callable.call()接口函数
Future<String> future = executor.submit(() ->{
Thread.sleep(200);
return "Hello World";
});
// 轮询调用isDone()来判断task是否完成,此函数调用也会浪费CPU资源
while(!future.isDone()) {
System.out.println("等待异步结果");
}
// 阻塞式获得异步执行结果,Future+callable比Thread和Runnable比可以多获取返回结果
System.out.println("异步结果:" + future.get());
}
}
1.2 CompletableFuture
和Future相比,CompletableFuture多了结果函数的回调,就不需要像使用Future一样手动轮询或者阻塞式查询task结果。
import java.util.concurrent.CompletableFuture;
public class CompletableFutureTest {
public static void main(String[] args) {
CompletableFuture<Double> cf = CompletableFuture.supplyAsync(CompletableFutureTest::get);
cf.thenAccept((result) -> {
System.out.println("result: " + result);
});
}
static Double get() {
return Math.random() * 20;
}
}
二、kotlin语言
2.1 多线程
测试的执行代码如下所示,使用kotlinc -script multiThreads.kts
来执行代码。
import kotlin.concurrent.thread
fun main() {
println("启动主线程...: ${Thread.currentThread().name}")
thread {
runTask()
}
println("结束主线程...: ${Thread.currentThread().name}")
}
fun runTask(){
println("启动线程...: ${Thread.currentThread().name}")
Thread.sleep(1000)
println("结束线程 ...: ${Thread.currentThread().name}")
}
main()
此代码的输出结果如下所示。
启动主线程...: main
结束主线程...: main
启动线程...: Thread-2
结束线程 ...: Thread-2
2.2 coroutines协程
kotlin执行corountines
需要安装额外的三方包,但由于kotlin的脚本执行形式没有太好的三方包依赖管理工具,建议安装kscript工具来执行kotlin测试脚本。
#!/usr/bin/env kscript
@file:DependsOn("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4")
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlin.concurrent.thread
fun main() = runBlocking {
println("启动主线程: ${Thread.currentThread().name}")
for(i in 1..10) {
launch {
runTask()
}
}
println("结束主线程...: ${Thread.currentThread().name}")
}
suspend fun runTask() {
println("启动协程...: ${Thread.currentThread().name}")
Thread.sleep(1000)
println("结束协程...: ${Thread.currentThread().name}")
}
main()
输出的执行结果如下所示,从测试结果看,kotlin语言中的coroutines
也是在单线程中调度切换。
启动主线程: main
结束主线程...: main
启动协程...: main
结束协程...: main
启动协程...: main
结束协程...: main
启动协程...: main
结束协程...: main
启动协程...: main
结束协程...: main
启动协程...: main
结束协程...: main
启动协程...: main
结束协程...: main
启动协程...: main
结束协程...: main
启动协程...: main
结束协程...: main
启动协程...: main
结束协程...: main
启动协程...: main
结束协程...: main
三、Go语言
3.1 Goroutine
Goroutine
是由runtime运行时进行调度执行。
package main
import (
"fmt"
"time"
"golang.org/x/sys/unix"
)
func runTask() {
fmt.Println("启动goroutine...:", unix.Gettid())
fmt.Println("结束goroutine...:", unix.Gettid())
}
func main() {
fmt.Println("启动主线程...:", unix.Gettid())
for i := 0; i < 10; i++ {
go runTask()
}
time.Sleep(time.Second)
fmt.Println("结束主线程...:", unix.Gettid())
}
实际输出结果为如下所示,从测试结果看,goroutines
是在runtim运行时对goroutine和系统调度进行了管理。
启动主线程...: 6694
启动goroutine...: 6694
结束goroutine...: 6694
启动goroutine...: 6694
结束goroutine...: 6694
启动goroutine...: 6694
结束goroutine...: 6694
启动goroutine...: 6694
结束goroutine...: 6694
启动goroutine...: 6694
结束goroutine...: 6694
启动goroutine...: 6694
结束goroutine...: 6694
启动goroutine...: 6694
结束goroutine...: 6694
启动goroutine...: 6697
结束goroutine...: 6697
启动goroutine...: 6696
启动goroutine...: 6698
结束goroutine...: 6698
结束goroutine...: 6697
结束主线程...: 6694
四、静态检查检查异步编程问题
pmd、findbugs和sonarqube暂时未找到。
同步/异步 vs 阻塞/非阻塞
- 同步/异步:是对于两个线程而言;
- 阻塞/非阻塞:是一个线程内的状态;
五、参考文献
- 廖雪峰java教程
- Java异步编程指南
- Java响应式编程介绍-以Spring WebFlux为例
- thenApply() vs thenApplyAsync()
- Java8——异步编程
- CompletableFuture原理与实践-外卖商家端API的异步化
- kotlin菜鸟教程
- Understanding Kotlin Coroutines
- Is it nescessary to limit the number of go routines in an entirely cpu-bound workload?
- 彻底理解同步 异步 阻塞 非阻塞