JAVA19新特性 代码实战

结构化并发

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作用域对象,然后派生出新的子线程。这种方式有以下改进:

  • 任务、子任务被包含在同一个单元中,派生出来的子任务在新的虚拟线程中执行
  • 任意子任务发生错误,其它子任务会被取消
  • 父任务被取消,所有的子任务也将被取消
  • 任务的调用层次结构在线程 dump文件中可见

预分配HASH

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);
}

模式匹配

switch模式匹配

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 ...
  }
}

FFM API

该特性主要的目的是为了替换笨重、易出错、执行效率慢的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需要做相关的设置。

–enable-preview --add-modules jdk.incubator.concurrent
JAVA19新特性 代码实战_第1张图片

你可能感兴趣的:(java,基础,java,开发语言)