异步编程学习

Posted by Shi Hai's Blog on December 24, 2021

异步和同步相比能充分的利用好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 阻塞/非阻塞

  • 同步/异步:是对于两个线程而言;
  • 阻塞/非阻塞:是一个线程内的状态;

五、参考文献