一 概念
官方解释:结构化并发将不同线程中运行的相关任务视为单个工作单元,从而简化错误处理和取消,提高了可靠性,并增强了可观察性。
个人理解:结构化并发是一种编程风格,目的是简化并发编程降低出错风险。官方把这种风格标准化为一个模版放进了自己的API中。
1. 传统并发弊端
在传统的java并发编程中,线程之间并不存在从属关系,当业务分布在不同的线程中,线程又相互嵌套或等待时,这会使得管理各线程生命周期的难度加大,稍有不慎就会造成线程泄露和取消延迟的风险。
private static void threadTest(){
System.out.println("--start--");
var thread1 = Thread.ofPlatform().start(()->{
var thread2 = Thread.ofPlatform().start(()->{
for(int i=0;i<10;i++){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread2: " + i);
}
System.out.println("thread2: END");
});
for(int i=0;i<10;i++){
if(!Thread.currentThread().isInterrupted()){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("thread1: " + i);
}
}
System.out.println("thread1: END");
});
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread1.interrupt();
System.out.println("--end--");
}
--start--
thread2: 0
thread1: 0
thread1: 1
thread2: 1
thread1: 2
thread2: 2
thread2: 3
thread1: 3
--end--
thread1: 4
thread1: END
thread2: 4
thread2: 5
thread2: 6
thread2: 7
thread2: 8
thread2: 9
thread2: END
上述例子是在线程thread1中创建并启动了线程thread2,当两个线程都还处于活动状态时中断了线程thread1。根据运行结果可以看出即使thread1停止后,thread2并没有停止。这样会导致线程泄露。并且可以看出线程thread1被中断后任然输出了一些内容后才停止的,这样的中断不及时会导致一些不可预料的错误。
2. 结构化并发优势
从上述实例可以得出我们最至少需要解决的两个问题:一是线程泄露,需要让子线程和父线程有联级关系;二是取消延迟,不能以标志位的形式判断状态;
结构化并发就是为了提倡一种可以消除取消和关闭带来的常见风险的并发编程风格,并提高并发代码的可观察性。
二 实现方案
1. StructuredTaskScope --Java
在原本的JDK中并不存在结构化并行的接口,用户可以通过使用众多的线程框架或者搭建自己的线程模版来达到结构化并行的目的。但实际上早在JDK19之前就已经在孵化,并在更新的JDK21中正式出现。
private static void threadTest() {
System.out.println("--start--");
try(var scope = new StructuredTaskScope<Object>()){
scope.fork(()->{
scope.fork(()->{
for(int i=0;i<10;i++){
Thread.sleep(100);
System.out.println("task2: " + i);
}
System.out.println("task2: END");
return null;
});
for(int i=0;i<10;i++){
Thread.sleep(100);
System.out.println("task1: " + i);
}
System.out.println("task1: END");
return null;
});
Thread.sleep(500);
scope.shutdown();
System.out.println("scope: SHUTDOWN");
scope.join();
}catch (Exception e){
System.out.println("Exception: " + e.getMessage());
}
System.out.println("--end--");
}
task2: 0
task1: 0
task2: 1
task1: 1
task2: 2
task1: 2
task2: 3
task1: 3
scope: SHUTDOWN
--end--
2. 协程 --Kotlin
private fun f() = runBlocking{
println("--start--")
val job1 = launch {
launch {
repeat(10){
delay(100)
println("job2: $it")
}
println("job2: END")
}
repeat(10){
delay(100)
println("job1: $it")
}
println("job1: END")
}
delay(500)
job1.cancel()
println("--end--")
}
--start--
job1: 0
job2: 0
job1: 1
job2: 1
job1: 2
job2: 2
job1: 3
job2: 3
--end--