structured concurrency,结构化并发是Java19的新特性,旨在解决多个子任务原子执行的问题,多个子任务之间要么要么全部执行成功,要么失败回滚取消任务。先来看一下问题代码
package com.feature.day02;
import java.util.concurrent.*;
public class StructConcurrency01 {
private final ExecutorService executor = Executors.newCachedThreadPool();
public static void main(String[] args) throws Exception{
String result = new StructConcurrency01().getDogBySubTask();
System.out.println(result);
}
String getDogBySubTask() throws ExecutionException, InterruptedException {
Future<String> name = executor.submit(() -> this.getName());
Future<String> breed = executor.submit(() -> this.dogBreed());
return "it's name is:" + name.resultNow() + ", and is a " + breed.resultNow();
}
String getName(){
int data = 1 / 0;
System.out.println("execute getName function");
return "Max";
}
// 在系统报错的情况下 这个方法肯定会执行
String dogBreed() throws Exception{
TimeUnit.SECONDS.sleep(1l);
System.out.println("execute dogBreed function");
return "Golden retriever";
}
}
上述代码,在一般情况下能够正常执行,但是依然存在不少问题:
以上这种情况都有可能发生,整个任务的维护需要相当复杂的代码。
JDK19中提供新的API,称为结构化并发,它的目的就是改进代码实现、可读性好、可维护性好 用以满此类问题的场景。
package com.feature.day02;
import jdk.incubator.concurrent.StructuredTaskScope;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class StructConcurrency02 {
public static void main(String[] args) throws Exception{
String result = new StructConcurrency02().getDogBySubTask();
System.out.println(result);
}
String getDogBySubTask() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()){
Future<String> name = scope.fork(() -> this.getName());
Future<String> breed = scope.fork(() -> this.dogBreed());
scope.join();
scope.throwIfFailed();
return "it's name is:" + name.resultNow() + ", and is a " + breed.resultNow();
}
}
String getName(){
int da = 1 / 0;
System.out.println("execute getName function");
return "Max";
}
String dogBreed() throws Exception{
TimeUnit.SECONDS.sleep(1l);
System.out.println("execute dogBreed function");
return "Golden retriever";
}
}
上述代码必须等待所有子任务执行完成后,才会返回最终结果。那么有没有可能,在一些特殊的业务场景中,只需要返回任一子任务的结果即可(即谁先执行完成,先返回谁),请看以下代码:
package com.feature.day02;
import jdk.incubator.concurrent.StructuredTaskScope;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
public class StructConcurrency03 {
public static void main(String[] args) throws Exception{
String result = new StructConcurrency03().getDogBySubTask();
System.out.println(result);
}
//返回优先完成的子任务结果即可
String getDogBySubTask() throws ExecutionException, InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<>()){
scope.fork(() -> this.getName());
scope.fork(() -> this.dogBreed());
scope.join();
return scope.result().toString();
}
}
String getName(){
System.out.println("execute getName function");
return "Max";
}
String dogBreed() throws Exception{
TimeUnit.SECONDS.sleep(1l);
System.out.println("execute dogBreed function");
return "Golden retriever";
}
}
结构化并发将线程的生存期绑定到创建它的代码块中,绑定操作在try 语句中获取,得到StructuredTaskScope作用域对象,然后派生出新的子线程。这种方式有以下改进:
List<String> list = new ArrayList<>(120);
上述代码 在声明List对象的同时,指定元素大小, 好处是在插入120个数据的时候不必进行多次扩容(List 默认的容量大小为10).
类似的问题在HASHMAP 对象初始化也存在
Map<String, Integer> map = new HashMap<>(120);
乍一看,根据初始化代码会认为Map内部提供了120个存储空间。由于Map内部特殊机制,实际上能够在存储90个元素的时候就会进行扩容,计算公式为:
120 × 0.75 = 90
// 0.75为 map默认的加载因子
因此当需要存储120个元素时,正确初始化的姿势应该为
// for 120 mappings: 120 / 0.75 = 160
Map<String, Integer> map = new HashMap<>(160);
但是,JDK19为解决上述问题,提供了更为简单的声明方式
Map<String, Integer> map = HashMap.newHashMap(120);
newHashMap 方法内部的源码:
public static <K, V> HashMap<K, V> newHashMap(int numMappings) {
return new HashMap<>(calculateHashMapCapacity(numMappings));
}
static final float DEFAULT_LOAD_FACTOR = 0.75f;
static int calculateHashMapCapacity(int numMappings) {
return (int) Math.ceil(numMappings / (double) DEFAULT_LOAD_FACTOR);
}
JAVA17中引进新特性,允许在switch语句中编写如下代码
switch (obj) {
case String s && s.length() > 5 -> System.out.println(s.toUpperCase());
case String s -> System.out.println(s.toLowerCase());
case Integer i -> System.out.println(i * i);
default -> {}
}
JAVA19中为了改变 “String s && s.length() > 5” 这种代码引入了新的关键字 when, 需要注意的是 when关键字只有在switch语句中才起作用,这样做的目的是不需要更改已经存在 用 when 定义好的变量。
Object obj = "hello";
switch (obj) {
case String s when s.length() > 5 -> System.out.println(s.toUpperCase());
case String s -> System.out.println(s.toLowerCase());
case Integer i -> System.out.println(i * i);
default -> {}
}
假设有以下代码
public record Position(int x, int y) {}
此时,需要提供一个对象打印的功能,包含Position对象
private void print(Object object) {
// instanceof Position position 为jdk16 的新特性
if (object instanceof Position position) {
System.out.println("object is a position, x = " + position.x()
+ ", y = " + position.y());
}
// else ...
}
JAVA19 增强了 instanceof 语句的语义 ,让代码更加简洁、优雅
private void print(Object object) {
if (object instanceof Position(int x, int y)) {
System.out.println("object is a position, x = " + x + ", y = " + y);
}
// else ...
}
在switch语句中,JAVA19 也增强了记录匹配的语义
// old code
private void print(Object object) {
switch (object) {
case Position position
-> System.out.println("object is a position, x = " + position.x()
+ ", y = " + position.y());
// other cases ...
default -> {}
}
}
// new code
private void print(Object object) {
switch (object) {
case Position(int x, int y)
-> System.out.println("object is a position, x = " + x + ", y = " + y);
// other cases ...
default -> {}
}
}
定义一个方法,两个参数的类型一样,代码如下
public record Path(Position from, Position to) {}
利用上面提到的记录匹配的特性,在可以将对象输出的代码改为
private void print(Object object) {
switch (object) {
case Path(Position(int x1, int y1), Position(int x2, int y2))
-> System.out.println("object is a path, x1 = " + x1 + ", y1 = " + y1
+ ", x2 = " + x2 + ", y2 = " + y2);
// other cases ...
}
}
该特性主要的目的是为了替换笨重、易出错、执行效率慢的JNI本地方法调用。从JDK14起,后续的每个版本都有改进、迭代。下面展示一个简单的DEMO。
package com.feature.day02;
import java.lang.foreign.FunctionDescriptor;
import java.lang.foreign.Linker;
import java.lang.foreign.MemorySegment;
import java.lang.foreign.SymbolLookup;
import java.lang.invoke.MethodHandle;
import static java.lang.foreign.SegmentAllocator.implicitAllocator;
import static java.lang.foreign.ValueLayout.ADDRESS;
import static java.lang.foreign.ValueLayout.JAVA_LONG;
public class FFMTest {
public static void main(String[] args) throws Throwable {
SymbolLookup stdlib = Linker.nativeLinker().defaultLookup();
final MethodHandle strlen = Linker.nativeLinker().downcallHandle(
stdlib.lookup("strlen").orElseThrow(),
FunctionDescriptor.of(JAVA_LONG, ADDRESS));
final MemorySegment str = implicitAllocator().allocateUtf8String("Happy Coding!");
long len = (long) strlen.invoke(str);
System.out.println("len = " + len);
}
}
代码的效果,是调用本地方法获取字符串的长度。
JAVA19新特性大部分为预览版,并不建议在生产环境中使用,因此在学习的过程中,IDEA需要做相关的设置。