Java 19 于 2022 年 9 月 20 日发布。
https://mail.openjdk.org/pipermail/jdk-dev/2022-September/006933.html
这次官方介绍的功能很少,但Record Patterns和Virtual Thread是值得关注的功能。
点击这里了解详情
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 可供下载。)
- 甲骨文*操作外部服务器需要付费许可证。
- 铁木兰收养
- 阿祖尔祖鲁
- 利比里亚 JDK
- 亚马逊 Corretto 19
- OpenJDK 的 Microsoft 构建
该更新将于 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"
当你想用default和null做同样的处理时,你不能用箭头样式写吗?针对这个问题,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)嗯,你不知道要等多久才能知道规格,即使你知道规格,也不直观。
不推荐使用语言环境构造函数
请改用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 允许ofPlatform 或ofVirtual 分离平台线程和虚拟线程。
使用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,马上又回到了原来的状态。
启动 100 个虚拟线程,每个等待 3 秒。
jshell> IntStream.range(0, 100).forEach(i -> Thread.ofVirtual().start(() -> s()))
然后你增加 17 个线程到 31 个线程。
您可以看到它没有增加 100,并且即使在它完成后仍有线程。
现在让我们同时运行 100,000 个线程。
从虚拟线程开始。
jshell> IntStream.range(0, 100_000).forEach(i -> Thread.ofVirtual().start(() -> s()))
10:09 运行。线程数保持在 31,CPU 利用率在 7% 左右。它使用大约 120MB 的内存。
我将在平台线程中尝试它。
jshell> IntStream.range(0, 100_000).forEach(i -> Thread.ofPlatform().start(() -> s()))
首先,它不会在一段时间内完成处理。
线程最多使用不到 20,000 个线程,但是当线程用完少于 20,000 个线程时,会使用另一个线程,并重复此操作直到可以处理 100,000 个线程。
此外,CPU 使用率高达近 80%。由于处理只是休眠,因此推测 CPU 用于线程切换。它不使用太多内存。
这样,在处理大量线程时,平台线程对同时处理的进程数有限制,会占用额外的 CPU 资源,因此需要并实现轻量级的虚拟线程。
JEP 428:结构化并发(孵化器)
在并行处理中,当执行多个进程时,当两个进程都完成时,进程正常结束,当其中一个进程结束时,进程结束,或者当任一进程发生异常时,进程结束。
但是,如果你尝试用现有的join、wait 等来控制它,代码实际上会写一个从join 到wait 的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