Java 19 于 2022 年 9 月 20 日发布。

https://mail.openjdk.org/pipermail/jdk-dev/2022-September/006933.html
这次官方介绍的功能很少,但Record Patterns和Virtual Thread是值得关注的功能。

我计划从 21 日星期三 21:10 左右开始在这里解释。
Java 19新機能まとめ

点击这里了解详情
JDK 19 发行说明
Java SE 19 平台 JSR 394
OpenJDK JDK 19 GA 发布

API 文档在这里
概述(Java SE 19 和 JDK 19)

这是添加的 API 列表
https://docs.oracle.com/en/java/javase/19/docs/api/new-list.html

这是 API 差异。
https://cr.openjdk.java.net/~iris/se/19/latestSpec/apidiffs/overview-summary.html

在 Mac 或 Linux 上安装开发者!推荐

除了 Oracle OpenJDK,可免费用于商业用途的发行版包括: (有些尚不可用。截至 9 月 22 日,Oracle JDK、Azul Zulu、Corretto 19 可供下载。)

该更新将于 10 月发布 19.0.1,1 月发布 19.0.2。

JEP

JEP 中总结了较大的变化。
https://openjdk.java.net/projects/jdk/19/

这次捕获了七辆 JEP。有3个已经出现在预览等中,4个是新合并的。然而,只有一个被正式抓获。

JEP 405:记录模式(预览)
JEP 422:Linux/RISC-V 端口
JEP 424:外部函数和内存 API(预览版)
JEP 425:虚拟线程(预览版)
JEP 426:Vector API(第四个孵化器)
JEP 427:开关的模式匹配(第三次预览)
JEP 428:结构化并发(孵化器)

工具

这次没有与工具相关的更改。

语言功能

至于语言功​​能更改,记录模式现在处于预览状态。此外,开关的模式匹配在第 3 次预览中。

JEP 405:记录模式(预览)
JEP 427:开关的模式匹配(第三次预览)

JEP 405:记录模式(预览)

可以分解记录的模式匹配。
例如,准备一条名为 Point 的记录。

jshell> record Point(int x, int y) {}
|  次を作成しました: レコード Point

jshell> var p = new Point(3, 5)
p ==> Point[x=3, y=5]

然后你可以写一个这样的模式:

jshell> p instanceof Point(var xx, var yy)
$3 ==> true

结合条件表达式,您可以在模式匹配为真时处理变量的值。

jshell> p instanceof Point(var xx, var yy) ?
   ...>   "x:%d y:%d".formatted(xx, yy) :
   ...>   "no match"
$4 ==> "x:3 y:5"

无法指定固定值。

jshell> p instanceof Point(3, var yy)
|  エラー:
|  型の開始が不正です
|  p instanceof Point(3, var yy)
|                     ^

定义一个带有点的盒子。

jshell> record Box(Point topLeft, Point bottomRight){}
|  次を作成しました: レコード Box

jshell> var b = new Box(new Point(3, 4), new Point(7, 9))
b ==> Box[topLeft=Point[x=3, y=4], bottomRight=Point[x=7, y=9]]

然后您可以直接检索 Point in Box 的值。

jshell> b instanceof Box(Point(var left, var top), Point(var right, var bottom)) ?
   ...>   "(%d, %d)-(%d, %d)".formatted(left, top, right, bottom) : "no match"
$8 ==> "(3, 4)-(7, 9)"

而不是命令式地获取记录的内容,比如

b instanceof Box b ?
  "(%d, %d)-(%d, %d)".formatted(b.topLeft().x(), b.topLeft().y(), 
                                b.bottomRight().x(), b.bottomRight().y()) : 
  "no match"

JEP 427:开关的模式匹配(第三次预览)

模式匹配现在可用于switch
现在是第三次预览。如果这次没有大的变化,它很可能会成为 Java 20 的官方特性。
Java 19 在使用时需要--enable-preview

“即便如此,模式匹配也不会经常出现,不是吗?”
你可能会这么认为,但它有很大的影响。

switch 可以写成case null

jshell> String s = null
s ==> null

jshell> switch (s) {
   ...>     case "test" -> "テスト";
   ...>     case null -> "ぬるぽ";
   ...>     default -> "hello";
   ...> }
$13 ==> "ぬるぽ"

如果缺少 case null,则出现 NullPointerException。

jshell> switch (s) {
   ...>     case "test" -> "テスト";
   ...>     default -> "hello";
   ...> }
|  例外java.lang.NullPointerException: Cannot invoke "String.hashCode()" because "<local0>" is null
|        at (#12:1)

""null 在进行相同处理时都写很方便。

jshell> switch(s){
   ...>     case null, "" -> "empty";
   ...>     default -> s;
   ...> }
$17 ==> "empty"

当你想用defaultnull做同样的处理时,你不能用箭头样式写吗?针对这个问题,default可以写成case

jshell> switch (s) {
   ...>     case "one" -> 1;
   ...>     case "two" -> 2;
   ...>     case null, default -> -1;
   ...> }
$21 ==> -1

因此,对于模式匹配,您现在可以在 swtich 中编写类型和变量。这称为类型模式。

jshell> Object o = "abc"
o ==> "abc"

jshell> switch (o) {
   ...>     case String s -> "--%s--".formatted(s);
   ...>     case Integer i -> "%03d".formatted(i);
   ...>     default -> o.toString();
   ...> }
$35 ==> "--abc--"

可以通过在类型模式后连接when 并编写条件来创建保护子句。

jshell> o = "helo"
o ==> "helo"

jshell> switch (o) {
   ...>     case String s when s.length() < 5 -> "--%s--".formatted(s);
   ...>     case String s -> s.toUpperCase();
   ...>     case Integer i -> "%05d".formatted(i);
   ...>     default -> o.toString();
   ...> }
$42 ==> "--helo--"

第二次预览使用&& 而不是when

switch (o) {
    case String s && s.length() < 5 -> "--%s--".formatted(s);
    case String s -> s.toUpperCase();
    case Integer i -> "%05d".formatted(i);
    default -> o.toString();
}

您还可以在一个开关中使用常量模式和类型模式。常量模式仍然可以是整数、字符串和枚举。

jshell> s = "helo"
s ==> "helo"

jshell> switch (s) {
   ...>     case "test" -> "テスト";
   ...>     case String s when s.length() < 5 -> s.toUpperCase();
   ...>     default -> "too long";
   ...> }
$29 ==> "HELO"

使用 case 子句类型模式定义的变量的范围仅在 case 子句内。
这里我们在switch之外定义了相同的变量s,并用case子句覆盖它,但是在switch之外没有任何作用。

jshell> s = "helo"
s ==> "helo"

jshell> switch (s) {
   ...>     case String s when s.length() < 5 -> (s="Hello").toUpperCase();
   ...>     default -> "too long";
   ...> }
$30 ==> "HELLO"

jshell> s
s ==> "helo"

与Record Patterns结合是最喜欢的。

return switch(n) {
    case IntExpr(int i) -> i;
    case NegExpr(Expr n) -> -eval(n);
    case AddExpr(Expr left, Expr right) -> eval(left) + eval(right);
    case MulExpr(Expr left, Expr right) -> eval(left) * eval(right);
    default -> throw new IllegalArgumentException(n);
};

蜜蜂

JEP 424:外部函数和内存 API(预览版)
JEP 425:虚拟线程(预览版)
JEP 426:Vector API(第四个孵化器)
JEP 428:结构化并发(孵化器)

小一个

BigDecimal. 两个

这可能是许多人会满意的唯一官方补充......

jshell> BigDecimal.TWO
$1 ==> 2

Thread.sleep(持续时间)

现在可以在 sleep 方法中指定持续时间,从而更容易编写。

jshell> Thread.sleep(Duration.ofSeconds(3))

sleep(3000)嗯,你不知道要等多久才能知道规格,即使你知道规格,也不直观。

不推荐使用语言环境构造函数

Java 19新機能まとめ

请改用of 方法。

jshell> Locale.of("ja")
$12 ==> ja

jshell> Locale.of("ja","jp")
$13 ==> ja_JP

HashMap / HashSet / LinkedHashMap / LinkedHashSet newXxx

分别增加了newHashMap等通过指定容量创建对象的静态方法。

jshell> Map<String, String> m = HashMap.newHashMap(10)
m ==> {}

new HashMap(10)的不同之处在于,没有<>,它不会说“未经检查的转换”,并且它有足够的容量来存储指定数量的元素,而不仅仅是指定的数量,不知道会不会。

jshell> Map<String, String> m = new HashMap(10)
|  警告:
|  無検査変換
|    期待値: java.util.Map<java.lang.String,java.lang.String>
|    検出値:    java.util.HashMap
|  Map<String, String> m = new HashMap(10);
|                          ^-------------^
m ==> {}

双精度/浮动精度

有效数字的精度位数。

jshell> Double.PRECISION
$2 ==> 53

jshell> Float.PRECISION
$3 ==> 24

数学.TAU

周长与半径之比。即 2π。

jshell> Math.TAU
$4 ==> 6.283185307179586

jshell> Math.TAU / 2
$5 ==> 3.141592653589793

jshell> Math.PI
$6 ==> 3.141592653589793

Integer.compress, 展开 / Long.compress, 展开

Commpress 仅提取在第二个参数中指定的位,用于第一个参数中指定的数字并将其向右移动。

jshell> Integer.toHexString(Integer.compress(0xcafebabe, 0xf000f0f0))
$15 ==> "cbb"

扩展则相反,将第一个参数中指定的数字的低位扩展为第二个参数中指定的位。

jshell> Integer.toHexString(Integer.expand(0xcbb, 0xf000f0f0))
$16 ==> "c000b0b0"

JEP 425:虚拟线程(预览版)

这是一个虚拟线程。自从 Java 在早期开始使用平台线程以来,它就一直使用平台线程作为线程。
平台线程是由操作系统管理的线程。由操作系统管理的线程旨在让您可以同时在 YouTube、Twitter 上观看视频、显示时钟和播放最终幻想。
当然,您在应用程序中运行线程做不同的事情,但正是这些线程做同样的事情最终产生了很多线程。
其中,要进行同样的计算,就必须考虑硬件配置,而硬件配置是GPU或Vector API处理的SIMD指令。
并且,在应用处理方面,通信等待时间比计算时间长得多,所以并行处理的主要目的是有效利用通信等待时间。
在这种情况下,在计算中间保持平衡的同时切换进程的平台线程质量过高,它们占用了大量内存,限制较低,并且执行它们想要做的事情很慢。
所以,目前,在 Java 中,我们正在编写称为 Reactive 和 Rx 的代码,可以在通信过程中将控制权传递给其他进程,但这非常困难,无法做到,所以 Java 有人正在远离,转向像 Kotlin 这样的语言那自然能写出这样的处理。

为了解决这个问题,由 JVM 而不是 OS 管理的线程被引入为虚拟线程。 Virtual Thread 是在 Project Loom 中开发的。

API 已经过精心设计,因此可以将虚拟线程视为现有平台线程。

但是,我能够在平台线程中创建一个线程为new Thread(),但我无法在构造函数中创建一个虚拟线程。

到目前为止,我已经使用了这样的平台线程:

jshell> Thread t = new Thread(() -> System.out.println("hello"))
t ==> Thread[#220496,Thread-110304,5,main]

jshell> t.getState()
$33 ==> NEW

jshell> t.start()

jshell> hello

jshell> t.getState()
$35 ==> TERMINATED

Java 19 提供 Thread.Builder 允许ofPlatformofVirtual 分离平台线程和虚拟线程。
使用unstarted 方法会给你一个尚未启动的线程,就像new Thread 一样。

jshell> Thread t = Thread.ofPlatform().unstarted(() -> System.out.println("hello"))
t ==> Thread[#220491,Thread-110303,5,main]

jshell> t.start()

hello
jshell> t.getState()
$24 ==> TERMINATED

并将其更改为ofVirtual 以获取虚拟线程。可以将虚拟线程视为 Thread 类的对象。用法类似于平台线程。

jshell> Thread t = Thread.ofVirtual().unstarted(() -> System.out.println("hello"))
t ==> VirtualThread[#220492]/new

jshell> t.start()

jshell> hello

jshell> t.getState()
$27 ==> TERMINATED

使用start 方法可以让线程同时运行和获取Thread 对象。

jshell> Thread t = Thread.ofVirtual().start(() -> System.out.println("hello"))
t ==> VirtualThread[#220497]/runnable
hello

jshell> t.getState()
$37 ==> TERMINATED

如果ExecutorService 使用虚拟线程,则使用Executors.newVirtualThreadPerTaskExecutor

jshell> ExecutorService ex = Executors.newVirtualThreadPerTaskExecutor()
ex ==> java.util.concurrent.ThreadPerTaskExecutor@28ba21f3
jshell> ex.submit(() -> System.out.println("hello"))
$40 ==> java.util.concurrent.ThreadPerTaskExecutor$ThreadBoundFuture@64a294a6[Not completed]
hello

jshell> ex.isTerminated()
$41 ==> false

jshell> ex.isShutdown()
$42 ==> false

jshell> ex.shutdown()
jshell> ex.isTerminated()
$44 ==> true

jshell> ex.isShutdown()
$45 ==> true

让我们来看看一些表现。
定义一个等待 3 秒的方法。

jshell> import java.time.Duration

jshell> void s(){ try{ Thread.sleep(Duration.ofSeconds(3));} catch(Exception ex){}}
|  次を作成しました: メソッド s()

启动 100 个平台线程,每个线程等待 3 秒。

jshell> IntStream.range(0, 100).forEach(i -> Thread.ofPlatform().start(() -> s()))

可以看到线程数增加了100,马上又回到了原来的状态。
Java 19新機能まとめ

启动 100 个虚拟线程,每个等待 3 秒。

jshell> IntStream.range(0, 100).forEach(i -> Thread.ofVirtual().start(() -> s()))

然后你增加 17 个线程到 31 个线程。

Java 19新機能まとめ

您可以看到它没有增加 100,并且即使在它完成后仍有线程。

现在让我们同时运行 100,000 个线程。
从虚拟线程开始。

jshell> IntStream.range(0, 100_000).forEach(i -> Thread.ofVirtual().start(() -> s()))

10:09 运行。线程数保持在 31,CPU 利用率在 7% 左右。它使用大约 120MB 的内存。

Java 19新機能まとめ

我将在平台线程中尝试它。

jshell> IntStream.range(0, 100_000).forEach(i -> Thread.ofPlatform().start(() -> s()))

首先,它不会在一段时间内完成处理。
线程最多使用不到 20,000 个线程,但是当线程用完少于 20,000 个线程时,会使用另一个线程,并重复此操作直到可以处理 100,000 个线程。
此外,CPU 使用率高达近 80%。由于处理只是休眠,因此推测 CPU 用于线程切换。它不使用太多内存。

Java 19新機能まとめ

这样,在处理大量线程时,平台线程对同时处理的进程数有限制,会占用额外的 CPU 资源,因此需要并实现轻量级的虚拟线程。

JEP 428:结构化并发(孵化器)

在并行处理中,当执行多个进程时,当两个进程都完成时,进程正常结束,当其中一个进程结束时,进程结束,或者当任一进程发生异常时,进程结束。
但是,如果你尝试用现有的joinwait 等来控制它,代码实际上会写一个从joinwait 的Go To,你将无法遵循该过程。
这就是结构化并行性发挥作用的地方。
像这样。我稍后会写更多关于它的内容!

Response handle() throws ExecutionException, InterruptedException {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        Future<String>  user  = scope.fork(() -> findUser());
        Future<Integer> order = scope.fork(() -> fetchOrder());

        scope.join();           // Join both forks
        scope.throwIfFailed();  // ... and propagate errors

        // Here, both forks have succeeded, so compose their results
        return new Response(user.resultNow(), order.resultNow());
    }
}

JEP 426:Vector API(第四个孵化器)

现在可以从 Java 中使用同时对多个数据执行计算的指令,例如 AVX 指令。
它是作为巴拿马项目之一开发的。
它是在 Java 16 中作为孵化器引入的,但现在它是第 4 个孵化器。

要使用它,您需要在运行时或编译时添加--add-modules jdk.incubator.vector

import jdk.incubator.vector.*;
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_256;

void vectorComputation(float[] a, float[] b, float[] c) {

    for (int i = 0; i < a.length; i += SPECIES.length()) { // SPECIES.length() = 256bit / 32bit -> 8
        VectorMask<Float> m = SPECIES.indexInRange(i, a.length); // 端数がマスクされる
                                                                 // a.lengthが11でiが8のとき最初の3つしか要素がないので [TTT.....]
		// FloatVector va, vb, vc;
        FloatVector va = FloatVector.fromArray(SPECIES, a, i, m);
        FloatVector vb = FloatVector.fromArray(SPECIES, b, i, m);
        FloatVector vc = va.mul(va).
                    add(vb.mul(vb)).
                    neg();
        vc.intoArray(c, i, m);
    }
}

有六种类型可供选择:每个都有对应的 Vector 类型,这是基础。

类型 位宽 向量
字节 8 字节向量
短的 16 短向量
整数 32 整数向量
64 长向量
漂浮 32 浮点向量
双倍的 64 双向量

但是,您需要 VectorSpecies 才能使用它。您要使用的 Vector 有 SPECIES_* 常量,因此请使用它们。 * 是一次计算的位数。

jshell> FloatVector.SP
SPECIES_128         SPECIES_256         SPECIES_512         SPECIES_64          SPECIES_MAX         SPECIES_PREFERRED   

MAX 是可以在该硬件上使用的最大值,PREFERRED 是推荐的位深度,但我想知道它是否会相同。这里推荐256bit,这样可以同时计算8个浮点数。

jshell> FloatVector.SPECIES_PREFERRED
$11 ==> Species[float, 8, S_256_BIT]

硬件可以使用的位数取决于安装的 CPU,但普通 Intel/AMD 为 256,XEON 和 Tsuyotsuyo CPU 为 512。 M1 为 128。如果您尝试使用硬件不支持的位数,它会很慢,因为它将是软件处理。

实际的 Vector 是使用 from* 方法获得的。提供了 fromArray、fromByteArray 和 fromByteBuffer。在我进入孵化器之前,有fromValues,但是没有了。

一旦你有一个向量,用提供的方法计算它。有一整套算术指令。

jshell> va.
abs()                   add(                    addIndex(               bitSize()               blend(                  
broadcast(              byteSize()              compare(                div(                    elementSize()           
elementType()           eq(                     equals(                 fma(                    getClass()              
hashCode()              intoArray(              intoByteArray(          intoByteBuffer(         lane(                   
lanewise(               length()                lt(                     max(                    min(                    
mul(                    neg()                   notify()                notifyAll()             pow(                    
rearrange(              reduceLanes(            reduceLanesToLong(      reinterpretAsBytes()    reinterpretShape(       
selectFrom(             shape()                 slice(                  sqrt()                  sub(                    
test(                   toArray()               toDoubleArray()         toIntArray()            toLongArray()           
toShuffle()             toString()              unslice(                viewAsFloatingLanes()   viewAsIntegralLanes()   
wait(                   withLane(      

顺便说一句,我觉得在这些方法调用内部调用AVX指令会很慢,但实际上,JIT编译器通过一种称为JVM内在函数的机制将这些方法调用替换为原生函数调用。

[JEP 424:外部函数和内存 API(预览版)]

它变成了 Preview 而不是 3rd 的孵化器,但是 Foreign Function API 的前身 Foreign Linker API 从 Java 16 开始就是一个孵化器,Foreign Memory API 从 Java 14 开始就是一个孵化器。所以也许他们已经停止给出数字了

外来内存 API

访问堆外内存有几种方式:使用ByteBuffer、使用Unsafe、使用JNI,各有优缺点。
使用带ByteBuffer的direct buffer时,限制为2GB,这是int可以处理的范围,内存释放依赖于GC。
Unsafe 有很好的性能,但并不像名字所暗示的那样安全,访问释放的内存会导致 JVM 崩溃。
使用JNI需要编写C代码,性能不好。

因此,在 Java 14 中引入了直接处理堆外内存的 API 作为孵化器模块,这是孵化器的第 6 个版本。
代码将如下所示:

import jdk.incubator.foreign.*;
import java.nio.ByteOrder;

VarHandle intHandle = MemoryHandles.varHandle(int.class, ByteOrder.nativeOrder());

try (MemorySegment segment = MemorySegment.allocateNative(100)) {
   for (int i = 0 ; i < 25 ; i++) {
        intHandle.set(seg, i * 4, i);
   }
}

您需要使用--add-modules jdk.incubator.foreign 加载模块进行编译和执行。
包也是jdk.incubator.foreign,就像模块名称一样。

WithOffset 和 withStride 从 MemoryHandles 中消失了。

jshell> import jdk.incubator.foreign.*

jshell> MemoryHandles.
asAddressVarHandle(   asUnsigned(           class                 collectCoordinates(   dropCoordinates(
filterCoordinates(    filterValue(          insertCoordinates(    permuteCoordinates(   varHandle(

Java 15 扩展了从 MemoryHandles 获取 VarHandle 的方法。

jshell> MemoryHandles.
asAddressVarHandle(   asUnsigned(           class                 collectCoordinates(   dropCoordinates(
filterCoordinates(    filterValue(          insertCoordinates(    permuteCoordinates(   varHandle(
withOffset(           withStride(

Java 14 只有三种方法:

jshell> MemoryHandles.
class         varHandle(    withOffset(   withStride(

Java 16 提供了一个名为 MemoryAccess 的实用程序,无需使用 VarHandle 即可轻松使用。

import jdk.incubator.foreign.*;

try (MemorySegment segment = MemorySegment.allocateNative(100)) {
   for (int i = 0 ; i < 25 ; i++) {
        MemoryAccess.setIntAtOffset(i * 4, i);
   }
}

外部函数 API

调用本机库。
使用 Foreign Memory Access API 访问外部存储器
JNI 的替代品

它成为 Java 16 中作为 Foreign Linker 的第一个孵化器,这是孵化器的第 4 个版本。

例如,我有一个如下所示的 C 函数:

size_t strlen(const char *s);

像这样取出MethodHandle

CLinker linker = CLinker.systemCLinker();
MethodHandle strlen = linker.downcallHandle(
    linker.lookup("strlen").get(),
    FunctionDescriptor.of(JAVA_LONG, ADDRESS)
);

好像是这样叫的。

MemorySegment str = implicitAllocator().allocateUtf8String("Hello");
long len          = strlen.invoke(cString);  // 5

JDK

作为JDK的变化,也就是发布政策等的变化,这次增加了对RISC-V的支持。

JEP 422:Linux/RISC-V 端口

支持带有 SIMD 指令的 64 位 RV64GV 配置。
它似乎还不支持 Vector API。
[vectorIntrinsics] RISC-V 的矢量 API


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308626532.html

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-12-05
  • 2022-01-09
  • 2021-07-06
  • 2021-11-07
猜你喜欢
  • 2021-07-12
  • 2022-12-23
  • 2021-09-03
  • 2022-01-02
  • 2021-10-05
  • 2022-12-23
  • 2022-12-23
相关资源
相似解决方案