1.Java 8 特性
1.Lambda 表达式
其他语言都已陆续推出Lambda 表达式,java也不落后。
Lambda 表达式简单来讲是一个匿名函数,Java 8 允许将函数作为参数传递到方法之中。
它是 Java 8 发布的最为重要的新特性之一,跟上了目前语言发展的趋势,是继泛型(Generics)和注解(Annotation)以来最大的突破。
采用 Lambda 表达式可以大幅简化代码的编写,开发人员在熟练之后,可以很简单的书写相关的功能,并且提升了相应的效率,这就是函数式编程。
2.Stream流
Stream流计算是一个跟当前很贴合的,随着计算能力提升及数据量的增加,这个是有必要的。有鉴于此,Java 新增了 java.util.stream 包,它和以前的流十分类似。以前我们最常接触到的是资源流,例如 java.io.FileInputStream,它通过流的方式把文件从一个地方输入到另一个地方,与文件内容无关,只是一种内容搬运工作,不涉及任何 CRUD 操作。
Java 8的两种流类型:
-
-
- stream 串行流
- parallelStream 并行流
-
Stream 的源数据来于 Collection、Arrays 等类型,因为它的方法参数都是函数式接口类型,所以通常和 Lambda 表达式一起使用。
Stream 不存储数据,也不改变源数据,但它提供了对集合数据进行检索和逻辑处理的功能,包括筛选、排序、统计、计数等。可以形象地将其比作 SQL 语句。
3.Optional
主要用来处理传入的数据的空指针NullPointerException问题。
假设有一个 Person 类,里面有个属性 Hobby,需求要获取 Hobby 的 scope。
class Person {
private Hobby hobby;
}
class Hobby {
private String scope;
}
以前这样写:
Person person = buildPerson();
if (person != null) {
Hobby hobby = person.getHobby();
if (hobby != null) {
String scope = hobby.getScope();
System.out.println(scope);
}
}
用 Optional 写:
Optional.ofNullable(person)
.map(p -> p.getHobby())
.map(h -> h.getScope())
.ifPresent(scope ->System.out.println(scope));
4.Interface
Interface 的初始设计目的是面向抽象,提高可扩展性。但这也带来了一个不足之处,即当 Interface 进行修改时,实现它的类也必须相应地进行修改。
为了解决 Interface 修改与现有实现不兼容的问题,现在可以使用 default 或 static 关键词修饰 interface 的方法,这样就可以给方法提供一个默认实现,而实现类无需重写此方法。
一个 Interface 中可以有多个方法被这两种关键词所修饰。二者的区别在于,default 方法是普通实例方法,可以使用 this 关键词调用,可以被子类继承和重写;而 static 方法与一般类的静态方法类似,无法被子类继承,只能通过 Interface 调用。
如果有一个类既实现了2个接口,都有相同名称的方法,并且两个接口没有继承关系的话,这时就必须重写同名方法()。不然的话,编译的时候就会报错。
public interface InterfacePerson {
static void staticInterfacePerson() {
System.out.println("InterfacePerson 的静态 staticInterfacePerson");
}
static void staticInterfacePerson2() {
System.out.println("InterfacePerson 的静态 staticInterfacePerson2");
}
default void defaultInterfacePerson() {
System.out.println("InterfacePerson 的默认 defaultInterfacePerson");
}
default void defaultInterfacePerson2() {
System.out.println("InterfacePerson 的默认 defaultInterfacePerson2");
}
}
public interface InterfacePerson2 {
static void staticInterfacePerson() {
System.out.println("InterfacePerson2 的静态 staticInterfacePerson");
}
default void defaultInterfacePerson() {
System.out.println("InterfacePerson2 的默认 defaultInterfacePerson");
}
}
当然我们也可以看到 Java 8 和 Java 7 接口的不同:
-
-
- Java 7 当中接口里面,只能含有常量和抽象方法
- Java 8 增加了默认与静态方法
-
以下是切换到 Java 7 的结果:
5.functional interface 函数式接口
定义:有且只有一个抽象方法,但可以有多个非抽象方法的接口。
在 Java 8 中专门有一个包放函数式接口 java.util.function,该包下的所有接口都有 @FunctionalInterface 注解,提供函数式编程。
在其他包中也有函数式接口,其中一些没有@FunctionalInterface 注解,但是只要符合函数式接口的定义就是函数式接口,与是否有@FunctionalInterface注解无关,注解只是在编译时起到强制规范定义的作用。其在 Lambda 表达式中有广泛的应用。
@FunctionalInterface
public interface TestFunction<T, U,E,F,G,GH,HV, R> {
R apply(T t, U u, E e, F f, G g, GH gh, HV hv);
}
6.Date-Time API
这是对java.util.Date强有力的补充,解决了 Date 类的大部分痛点:
-
-
- 非线程安全
- 时区处理麻烦
- 各种格式化、和时间计算繁琐
- 设计有缺陷,Date 类同时包含日期和时间;还有一个 java.sql.Date,容易混淆。
-
2.Java 9 特性
1.模块化系统
Java 9 的时候最大的一个新特性是模块化系统,模块系统的核心原理是将应用程序划分为一组模块,并通过 module-info.java 文件来定义模块的信息和依赖关系。
Java 应用可以通过新增的Jlink工具 (Jlink 是随 Java 9 一起发布的新命令行工具。JDK 9 引入的新特性允许开发人员为基于模块化的 Java 应用程序创建自定义、轻量级的 JRE(Java Runtime Environment)。开发人员可以根据所依赖的 JDK 模块创建自定义的运行时镜像,这将大大减少 Java 运行时环境的大小。
我们可以通过 exports 关键词控制模块及使用范围。
module limit.module {
//exports 公开指定包的所有公共成员
exports com.my.package.name;
}
module limit.module {
//exports…to 限制访问的成员范围
export com.limit.package.name to com.target.limit.package;
}
2.G1 成为默认垃圾回收器
在 Java 8 中,默认使用的垃圾回收器为 Parallel Scavenge(新生代)+Parallel Old(老年代)。但到了 Java 9,CMS 垃圾回收器被弃用,取而代之的是 G1(Garbage-First Garbage Collector),成为新的默认垃圾回收器。
实际上,G1 垃圾回收器在 Java 7 中就已经被引入,而经过两个版本的表现表现优异后,它于 Java 9 成为了默认的垃圾回收器。
3.接口私有方法
Java 9 允许在接口中使用私有方法:
public interface PrivateInterface {
private void privateM(){
}
}
4.快速创建不可变集合
增加了List.of()、Set.of()、Map.of() 和 Map.ofEntries()等工厂方法来创建不可变集合。使用 of() 创建的集合为不可变集合,不能进行添加、删除、替换、 排序等操作,不然会报 java.lang.UnsupportedOperationException 异常。
5.String 存储结构优化
Java 8 及之前的版本,String 一直是用 char[] 存储。在 Java 9 之后,String 的实现改用 byte[] 数组存储字符串,节省了空间。
6.try-with-resources 增强
在 Java 9 之前,我们只能在 try-with-resources 块中声明变量:
try (FileInputStream inputStream = new FileInputStream("a.txt");) {
} catch (IOException e) {
throw new RuntimeException(e);
}
在 Java 9 之后,在 try-with-resources 语句中可以使用 effectively-final 变量:
final FileInputStream inputStream = new FileInputStream("a.txt");
try (inputStream) {
} catch (IOException e) {
throw new RuntimeException(e);
}
什么是 effectively-final 变量? 如果对象或基础类型的变量在初始化后值不发生改变,则可以把它们看做 effectively final
7.Stream & Optional 增强
Stream 中增加了新的方法 ofNullable()、dropWhile()、takeWhile() 以及 iterate() 方法的重载方法。
8.进程 API
Java 9 增加了 java.lang.ProcessHandle 接口来实现对原生进程进行管理,尤其适合于管理长时间运行的进程。
9.响应式流(Reactive Streams)
在 Java 9 中的 java.util.concurrent.Flow 类中新增了反应式流规范的核心接口 。
Flow 中包含了 Flow.Publisher、Flow.Subscriber、Flow.Subscription 和 Flow.Processor 等 4 个核心接口。Java 9 还提供了SubmissionPublisher 作为Flow.Publisher 的一个实现。
10.变量句柄
变量句柄是一个变量或一组变量的引用,包括静态域,非静态域,数组元素和堆外数据结构中的组成部分等。
变量句柄的含义类似于已有的方法句柄 MethodHandle ,由 Java 类 java.lang.invoke.VarHandle 来表示,可以使用类 java.lang.invoke.MethodHandles.Lookup 中的静态工厂方法来创建 VarHandle 对象。
VarHandle 的出现替代了 java.util.concurrent.atomic 和 sun.misc.Unsafe 的部分操作。并且提供了一系列标准的内存屏障操作,用于更加细粒度的控制内存排序。在安全性、可用性、性能上都要优于现有的 API。
11.平台日志 API 改进
Java 9 允许为 JDK 和应用配置同样的日志实现。新增了 System.LoggerFinder 用来管理 JDK 使 用的日志记录器实现。JVM 在运行时只有一个系统范围的 LoggerFinder 实例。我们可以通过添加自己的 System.LoggerFinder 实现来让 JDK 和应用使用 SLF4J 等其他日志记录框架。
12.CompletableFuture类增强
新增了几个新的方法(completeAsync ,orTimeout 等)。
13.I/O 流的新特性
增加了新的方法来读取和复制 InputStream 中包含的数据。
14.改进方法句柄(Method Handle)
方法句柄从 Java 7 开始引入,Java 9 在类java.lang.invoke.MethodHandles 中新增了更多的静态方法来创建不同类型的方法句柄。
3.Java 10 特性
1.局部变量类型推断(var)
Java 10 提供了 var 关键字声明局部变量。
var i = 0;
var url = new URL("https://mp.weixin.qq.com/");
var list = new ArrayList<>();
var map = new HashMap<String, String>();
for (var n : list){
System.out.print(n+ " ");
}
var不能声明为 null,不能声明为 Lambda表达式。
2.垃圾回收器接口
在早期的 JDK 结构中,组成垃圾收集器 (GC) 实现的组件分散在代码库的各个部分。 Java 10 通过引入一套纯净的垃圾收集器接口来将不同垃圾收集器的源代码分隔开。
3.G1 并行 Full GC
从 Java9 开始 G1 就了默认的垃圾回收器,G1 是以一种低延时的垃圾回收器来设计的,旨在避免进行 Full GC,但是 Java9 的 G1 的 FullGC 依然是使用单线程去完成标记清除算法,这可能会导致垃圾回收期在无法回收内存的时候触发 Full GC。
为了最大限度地减少 Full GC 造成的应用停顿的影响,从 Java10 开始,G1 的 FullGC 改为并行的标记清除算法,同时会使用与年轻代回收和混合回收相同的并行工作线程数量,从而减少了 Full GC 的发生,以带来更好的性能提升、更大的吞吐量。
4.集合增强
List,Set,Map 提供了静态方法copyOf()返回入参集合的一个不可变拷贝。
static <E> List<E> copyOf(Collection<? extends E> coll) {
return ImmutableCollections.listCopy(coll);
}
使用 copyOf() 创建的集合为不可变集合,不能进行添加、删除、替换、 排序等操作,不然会报 java.lang.UnsupportedOperationException 异常,并且 java.util.stream.Collectors 中新增了静态方法,用于将流中的元素收集为不可变的集合。
var list = new ArrayList<>();
list.stream().collect(Collectors.toUnmodifiableList());
list.stream().collect(Collectors.toUnmodifiableSet());
5.Optional 增强
Optional 新增了orElseThrow()方法来在没有值时抛出指定的异常。
Optional.ofNullable(cache.getIfPresent(key))
.orElseThrow(() -> new PersonalException( "personal Exception for key: " + key));
6.应用程序类数据共享(扩展 CDS 功能)
在 Java 5 中就已经引入了类数据共享机制 (Class Data Sharing,简称 CDS),允许将一组类预处理为共享归档文件,以便在运行时能够进行内存映射以减少 Java 程序的启动时间,当多个 Java 虚拟机(JVM)共享相同的归档文件时,还可以减少动态内存的占用量,同时减少多个虚拟机在同一个物理或虚拟的机器上运行时的资源占用。CDS 在当时还是 Oracle JDK 的商业特性。
Java 10 在现有的 CDS 功能基础上再次拓展,以允许应用类放置在共享存档中。CDS 特性在原来的 bootstrap 类基础之上,扩展加入了应用类的 CDS 为 (Application Class-Data Sharing,AppCDS) 支持,大大加大了 CDS 的适用范围。其原理为:在启动时记录加载类的过程,写入到文本文件中,再次启动时直接读取此启动文本并加载。设想如果应用环境没有大的变化,启动速度就会得到提升。
7.线程-局部管控
Java 10 中线程管控引入 JVM 安全点的概念,将允许在不运行全局 JVM 安全点的情况下实现线程回调,由线程本身或者 JVM 线程来执行,同时保持线程处于阻塞状态,这种方式使得停止单个线程变成可能,而不是只能启用或停止所有线程
8.备用存储装置上的堆分配
Java 10 中将使得 JVM 能够使用适用于不同类型的存储机制的堆,在可选内存设备上进行堆内存分配。
4.Java 11 特性
1.HTTP Client 标准化
Java 11 标准化了 Java 9 中引入的 Http Client API,并在 Java 10 中进行了更新。在前两个版本中对 Http Client 进行孵化的同时,该 API 几乎被完全重写,并且现在已经完全支持异步非阻塞。
此外,Java 11 中,Http Client 的包名已由 jdk.incubator.http 更改为 java.net.http。该 API 通过 CompleteableFuture 提供了非阻塞请求和响应语义。使用起来也非常简单,例如:
var request = HttpRequest.newBuilder()
.uri(URI.create("https://javastack.cn"))
.GET()
.build();var client = HttpClient.newHttpClient();
// 同步
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());System.out.println(response.body());
// 异步
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
.thenApply(HttpResponse::body)
.thenAccept(System.out::println);
2.启动单文件源代码程序
这意味着我们可以运行单一文件的 Java 源代码。此功能允许使用 Java 解释器直接执行 Java 源代码。源代码在内存中编译,然后由解释器执行,不需要在磁盘上生成 .class 文件了。唯一的约束在于所有相关的类必须定义在同一个 Java 文件中。
对于 Java 初学者并希望尝试简单程序的人特别有用,并且能和 jshell 一起使用。一定能程度上增强了使用 Java 来写脚本程序的能力。
3.新的垃圾回收器 Epsilon
一个完全消极的 GC 实现,分配有限的内存资源,最大限度的降低内存占用和内存吞吐延迟时间。
4.低开销的 Heap Profiling
Java 11 中提供一种低开销的 Java 堆分配采样方法,能够得到堆分配的 Java 对象信息,并且能够通过 JVMTI 访问堆信息。
5.飞行记录器(Java Flight Recorder)
飞行记录器之前是商业版 JDK 的一项分析工具,但在 Java 11 中,其代码被包含到公开代码库中,这样所有人都能使用该功能了。
6.String 增强
Java 11 增加了一系列的字符串处理方法:
//判断字符串是否为空
System.out.println(" ".isBlank());
// 去除字符串首尾空格
System.out.println("结果:"+" 去除字符串首尾空格 ".strip()+"结束");
// 去除字符串首部空格
System.out.println("结果:"+"去除字符串首部空格 ".stripLeading()+"结束");
// 去除字符串尾部空格
System.out.println("结果:"+" 去除字符串尾部空格 ".stripTrailing()+"结束");
// 重复字符串多少次
System.out.println(" 重复字符串多少次 ".repeat(2));
// 返回由行终止符分隔次数。
System.out.println("A\nB\nC".lines().count());
// 返回由行终止符分隔的字符串集合。
System.out.println("A\nB\nC".lines().collect(Collectors.toList()));
输出:
7.Optional 增强
新增了empty()方法来判断指定的 Optional 对象是否为空:
var obj= Optional.empty();
//判断指定的 Optional 对象是否为空
System.out.println(obj.isEmpty());
8.Lambda 参数的局部变量语法
从 Java 10 开始,便引入了局部变量类型推断这一关键特性。类型推断允许使用关键字 var 作为局部变量的类型而不是实际类型,编译器根据分配给变量的值推断出类型。
Java 10 中对 var 关键字存在几个限制:
-
-
- 只能用于局部变量上
- 声明时必须初始化
- 不能用作方法参数
- 不能在 Lambda 表达式中使用
-
Java11 开始允许开发者在 Lambda 表达式中使用 var 进行参数声明。
// 下面两者是等价的
Consumer<String> consumer = (var c) -> System.out.println(c);
Consumer<String> consumer = (String c) -> System.out.println(c);
5.Java 12 特性
1.数字格式化工具类
NumberFormat 新增了对复杂的数字进行格式化的支持:
NumberFormat fmtShort = NumberFormat.getCompactNumberInstance(Locale.CHINESE, NumberFormat.Style.SHORT);
String resultShort = fmtShort.format(10000000);
System.out.println(resultShort);
NumberFormat fmt2Short = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
String result2Short = fmt2Short.format(10000000);
System.out.println(result2Short);
NumberFormat fmtLong = NumberFormat.getCompactNumberInstance(Locale.CHINESE, NumberFormat.Style.LONG);
String resultLong = fmtLong.format(10000000);
System.out.println(resultLong);
NumberFormat fmt2Long = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.LONG);
String result2Long = fmt2Long.format(10000000);
System.out.println(result2Long);
输出:
2.Shenandoah GC
Redhat 主导开发的 Pauseless GC 实现,主要目标是 99.9% 的暂停小于 10ms,暂停与堆大小无关等。
和 Java 11 开源的 ZGC 相比(需要升级到 JDK 11 才能使用),Shenandoah GC 有稳定的 JDK 8u 版本,在 Java 8 占据主要市场份额的今天有更大的可落地性。
3.String 增强
Java 11 增加了两个的字符串处理方法:
1.indent() 方法可以实现字符串缩进:
public String indent(int n) {
if (isEmpty()) {
return "";
}
Stream<String> stream = lines();
if (n > 0) {
final String spaces = " ".repeat(n);
stream = stream.map(s -> spaces + s);
} else if (n == Integer.MIN_VALUE) {
stream = stream.map(s -> s.stripLeading());
} else if (n < 0) {
stream = stream.map(s -> s.substring(Math.min(-n, s.indexOfNonWhitespace())));
}
return stream.collect(Collectors.joining("\n", "", "\n"));
}
2.transform() 方法可以用来转变指定字符串:
public <R> R transform(Function<? super String, ? extends R> f) {
return f.apply(this);
}
如以下所示:
//indent() 方法可以实现字符串缩进。
String text = "缩进";
// 缩进 4 格
text = text.indent(4);
System.out.println(text);
//transform() 增加的直接使用的方法函数式编程方法。
String result = "源数据".transform(input -> input + " 增加");
System.out.println(result);
4.Files 增强(文件比较)
Java 12 添加了以下方法来比较两个文件:
public static long mismatch(Path path, Path path2) throws IOException
mismatch() 方法用于比较两个文件,并返回第一个不匹配字符的位置,如果文件相同则返回 -1L。
5.G1 收集器优化
Java12 为默认的垃圾收集器 G1 带来了两项更新:
-
-
- 可中止的混合收集集合:
- JEP344 的实现,为了达到用户提供的停顿时间目标,JEP 344 通过把要被回收的区域集(混合收集集合)拆分为强制和可选部分,使 G1 垃圾回收器能中止垃圾回收过程。 G1 可以中止可选部分的回收以达到停顿时间目标。
- 及时返回未使用的已分配内存:
- JEP346 的实现,增强 G1 GC,以便在空闲时自动将 Java 堆内存返回给操作系统。
- 可中止的混合收集集合:
-
6.Java 13 特性
1.增强 ZGC(释放未使用内存)
在 Java 11 中是实验性的引入的 ZGC 在实际的使用中存在未能主动将未使用的内存释放给操作系统的问题。
ZGC 堆由一组称为 ZPages 的堆区域组成。在 GC 周期中清空 ZPages 区域时,它们将被释放并返回到页面缓存 ZPageCache 中,此缓存中的 ZPages 按最近最少使用(LRU)的顺序,并按照大小进行组织。
在 Java 13 中,ZGC 将向操作系统返回被标识为长时间未使用的页面,这样它们将可以被其他进程重用。
2.SocketAPI 重构
Java Socket API 终于迎来了重大更新!
Java 13 将 Socket API 的底层进行了重写, NioSocketImpl 是对 PlainSocketImpl 的直接替代,它使用 java.util.concurrent 包下的锁而不是同步方法。如果要使用旧实现,请使用 -Djdk.net.usePlainSocketImpl=true。
并且,在 Java 13 中是默认使用新的 Socket 实现:
public final class NioSocketImpl extends SocketImpl implements PlatformSocketImpl {
}
3.FileSystems
FileSystems 类中添加了以下三种新方法,以便更容易地使用将文件内容视为文件系统的文件系统提供程序:
-
-
- newFileSystem(Path)
- newFileSystem(Path, Map<String, ?>)
- newFileSystem(Path, Map<String, ?>, ClassLoader)
-
4.动态 CDS 存档
Java 13 中对 Java 10 中引入的应用程序类数据共享(AppCDS)进行了进一步的简化、改进和扩展,即:允许在 Java 应用程序执行结束时动态进行类归档,具体能够被归档的类包括所有已被加载,但不属于默认基层 CDS 的应用程序类和引用类库中的类。
这提高了应用程序类数据共享(AppCDSopen in new window)的可用性。无需用户进行试运行来为每个应用程序创建类列表。
$ java -XX:ArchiveClassesAtExit=my_app_cds.jsa -cp my_app.jar
$ java -XX:SharedArchiveFile=my_app_cds.jsa -cp my_app.jar
7.Java 14 特性
1.空指针异常精准提示
通过 JVM 参数中添加-XX:+ShowCodeDetailsInExceptionMessages,可以在空指针异常中获取更为详细的调用信息,更快的定位和解决问题。
a.b.c.i = 99; // 假设这段代码会发生空指针
Java 14 之前:
Exception in thread "main" java.lang.NullPointerException
at NullPointerExample.main(NullPointerExample.java:5)
Java 14 之后:
// 增加参数后提示的异常中很明确的告知了哪里为空导致Exception in thread "main" java.lang.NullPointerException:
Cannot read field 'c' because 'a.b' is null.
at Prog.main(Prog.java:5)
Java 14 之后:
// 增加参数后提示的异常中很明确的告知了哪里为空导致Exception in thread "main" java.lang.NullPointerException:
Cannot read field 'c' because 'a.b' is null.
at Prog.main(Prog.java:5)
2.switch 的增强(转正)
Java 12 引入的 switch(预览特性)在 Java 14 变为正式版本,不需要增加参数来启用,直接在 JDK14 中就能使用。
Java 12 为 switch 表达式引入了类似 lambda 语法条件匹配成功后的执行块,不需要多写 break ,Java 13 提供了 yield 来在 block 中返回值。
3.ZGC支持
从 Java11 引入的 ZGC 作为继 G1 过后的下一代 GC 算法,从支持 Linux 平台到 Java14 开始支持 MacOS 和 Window
4.移除 CMS
移除了 CMS(Concurrent Mark Sweep) 垃圾收集器
5.新增了 jpackage 工具
jpackage 工具,标配将应用打成 jar 包外,还支持不同平台的特性包,比如 linux 下的deb和rpm,window 平台下的msi和exe
8.Java 15 特性
1.隐藏类(Hidden Classes)
隐藏类是为框架(frameworks)所设计的,隐藏类不能直接被其他类的字节码使用,只能在运行时生成类并通过反射间接使用它们。
2.TreeMap增强
TreeMap 新引入了下面这些方法:
-
-
- putIfAbsent()
- computeIfAbsent()
- computeIfPresent()
- compute()
- merge()
-
3.ZGC
Java11 的时候 ,ZGC 还在试验阶段。
当时,ZGC 的出现让众多 Java 开发者看到了垃圾回收器的另外一种可能,因此备受关注。
经过多个版本的迭代,不断的完善和修复问题,ZGC 在 Java 15 已经可以正式使用了!
不过,默认的垃圾回收器依然是 G1。你可以通过下面的参数启动 ZGC:
$ java -XX:+UseZGC className
4.文本块
在 Java 15 ,文本块是正式的功能特性了。
5.禁用和废弃偏向锁(Biased Locking)
偏向锁的引入增加了 JVM 的复杂性大于其带来的性能提升。不过,你仍然可以使用 -XX:+UseBiasedLocking 启用偏向锁定,但它会提示 这是一个已弃用的 API。
9.Java 16 特性
1.启用 C++ 14 语言特性
Java 16 允许在 JDK 的 C++ 源代码中使用 C++14 语言特性,并提供在 HotSpot 代码中可以使用哪些特性的具体指导。
在 Java 15 中,JDK 中 C++ 代码使用的语言特性仅限于 C++98/03 语言标准。它要求更新各种平台编译器的最低可接受版本。
2.ZGC 并发线程堆栈处理
Java16 将 ZGC 线程栈处理从安全点转移到一个并发阶段,甚至在大堆上也允许在毫秒内暂停 GC 安全点。消除 ZGC 垃圾收集器中最后一个延迟源可以极大地提高应用程序的性能和效率。
3.弹性元空间
自从引入了 Metaspace 以来,根据反馈,Metaspace 经常占用过多的堆外内存,从而导致内存浪费。弹性元空间这个特性可将未使用的 HotSpot 类元数据(即元空间,metaspace)内存更快速地返回到操作系统,从而减少元空间的占用空间。
4.打包工具
在 Java 14 中,JEP 343 引入了打包工具,命令是 jpackage。在 Java 15 中,继续孵化,现在在 Java 16 中,终于成为了正式功能。
5.instanceof 模式匹配(转正)
JDK 版本 | 更新类型 | JEP | 更新内容 |
---|---|---|---|
Java SE 14 | preview | JEP 305open in new window | 首次引入 instanceof 模式匹配。 |
Java SE 15 | Second Preview | JEP 375open in new window | 相比较上个版本无变化,继续收集更多反馈。 |
Java SE 16 | Permanent Release | JEP 394open in new window | 模式变量不在隐式为 final。 |
从 Java 16 开始,你可以对 instanceof 中的变量值进行修改:
String o = "";
// 原来
if (o instanceof String) {
String s = (String) o;
}
Object c = null;
c = "";
// 新的
if (c instanceof String b) {
b = "b";
System.out.println("c=" + c);
System.out.println("b=" + b);
}
6.记录类型
记录类型变更历史:
JDK 版本 | 更新类型 | JEP | 更新内容 |
---|---|---|---|
Java SE 14 | Preview | JEP 359open in new window | 引入 record 关键字,record 提供一种紧凑的语法来定义类中的不可变数据。 |
Java SE 15 | Second Preview | JEP 384open in new window | 支持在局部方法和接口中使用 record。 |
Java SE 16 | Permanent Release | JEP 395open in new window | 非静态内部类可以定义非常量的静态成员。 |
从 Java SE 16 开始,非静态内部类可以定义非常量的静态成员:
public class Outer {
class Inner {
static int age;
}
}
在 JDK 16 之前,如果写上面这种代码,IDE 会提示你静态字段 age 不能在非静态的内部类中定义,除非它用一个常量表达式初始化。
10.Java 17 特性
Java 17 将是继 Java 8 以来最重要的长期支持(LTS)版本,是 Java 社区八年努力的成果。Spring 6.x 和 Spring Boot 3.x ,Spring Cloud 2022.X 最低支持的就是 Java 17。
1.增强的伪随机数生成器
JDK 17 之前,我们可以借助 Random、ThreadLocalRandom和SplittableRandom来生成随机数。不过,这 3 个类都各有缺陷,且缺少常见的伪随机算法支持。
Java 17 为伪随机数生成器 (pseudorandom number generator,RPNG,又称为确定性随机位生成器)增加了新的接口类型和实现,使得开发者更容易在应用程序中互换使用各种 PRNG 算法。
RPNGopen in new window 用来生成接近于绝对随机数序列的数字序列。一般来说,PRNG 会依赖于一个初始值,也称为种子,来生成对应的伪随机数序列。只要种子确定了,PRNG 所生成的随机数就是完全确定的,因此其生成的随机数序列并不是真正随机的。
使用示例:
RandomGeneratorFactory<RandomGenerator> l128X256MixRandom = RandomGeneratorFactory.of("L128X256MixRandom");
// 使用时间戳作为随机数种子
RandomGenerator randomGenerator = l128X256MixRandom.create(System.currentTimeMillis());
// 生成随机数
randomGenerator.nextInt(10));
2.弃用 Applet API 以进行删除
Applet API 用于编写在 Web 浏览器端运行的 Java 小程序,很多年前就已经被淘汰了,已经没有理由使用了。
Applet API 在 Java 9 时被标记弃用(JEP 289open in new window),但不是为了删除。
3.删除远程方法调用激活机制
删除远程方法调用 (RMI) 激活机制,同时保留 RMI 的其余部分。RMI 激活机制已过时且不再使用。
4.密封类(转正)
密封类由 JEP 360open in new window 提出预览,集成到了 Java 15 中。在 JDK 16 中, 密封类得到了改进(更加严格的引用检查和密封类的继承关系),由 JEP 397open in new window 提出了再次预览。
5.删除实验性的 AOT 和 JIT 编译器
在 Java 9 的 JEP 295open in new window ,引入了实验性的提前 (AOT) 编译器,在启动虚拟机之前将 Java 类编译为本机代码。
Java 17,删除实验性的提前 (AOT) 和即时 (JIT) 编译器,因为该编译器自推出以来很少使用,维护它所需的工作量很大。保留实验性的 Java 级 JVM 编译器接口 (JVMCI),以便开发人员可以继续使用外部构建的编译器版本进行 JIT 编译。
11.Java 18 特性
1.默认字符集为 UTF-8
JDK 终于将 UTF-8 设置为默认字符集。
在 Java 17 及更早版本中,默认字符集是在 Java 虚拟机运行时才确定的,取决于不同的操作系统、区域设置等因素,因此存在潜在的风险。就比如说你在 Mac 上运行正常的一段打印文字到控制台的 Java 程序到了 Windows 上就会出现乱码,如果你不手动更改字符集的话。
2.简易的 Web 服务器
Java 18 之后,你可以使用 jwebserver 命令启动一个简易的静态 Web 服务器:
$ jwebserver
Binding to loopback by default. For all interfaces use "-b 0.0.0.0" or "-b ::".
Serving /cwd and subdirectories on 127.0.0.1 port 8000
URL: http://127.0.0.1:8000/
这个服务器不支持 CGI 和 Servlet,只限于静态文件。
3.优化 Java API 文档中的代码片段
在 Java 18 之前,如果我们想要在 Javadoc 中引入代码片段可以使用 <pre>{@code ...}</pre>:
<pre>{@code
lines of source code
}</pre>
<pre>{@code ...}</pre> 这种方式生成的效果比较一般。
在 Java 18 之后,可以通过 @snippet 标签来做这件事情:
/**
* The following code shows how to use {@code Optional.isPresent}:
* {@snippet :
* if (v.isPresent()) {
* System.out.println("v: " + v.get());
* }
* }
*/
@snippet 这种方式生成的效果更好且使用起来更方便一些。
4.使用方法句柄重新实现反射核心
Java 18 改进了 java.lang.reflect.Method、Constructor 的实现逻辑,使之性能更好,速度更快。这项改动不会改动相关 API ,这意味着开发中不需要改动反射相关代码,就可以体验到性能更好反射。
OpenJDK 官方给出了新老实现的反射性能基准测试结果:
新老实现的反射性能基准测试结果
5.互联网地址解析 SPI
Java 18 定义了一个全新的 SPI(service-provider interface),用于主要名称和地址的解析,以便 java.net.InetAddress 可以使用平台之外的第三方解析器。
12.Java 19 特性
1.虚拟线程(预览)
虚拟线程(Virtual Thread-)是 JDK 而不是 OS 实现的轻量级线程(Lightweight Process,LWP),许多虚拟线程共享同一个操作系统线程,虚拟线程的数量可以远大于操作系统线程的数量。
虚拟线程在其他多线程语言中已经被证实是十分有用的,比如 Go 中的 Goroutine、Erlang 中的进程。
虚拟线程避免了上下文切换的额外耗费,兼顾了多线程的优点,简化了高并发程序的复杂,可以有效减少编写、维护和观察高吞吐量并发应用程序的工作量。
2.结构化并发(孵化)
JDK 19 引入了结构化并发,一种多线程编程方法,目的是为了通过结构化并发 API 来简化多线程编程,并不是为了取代java.util.concurrent,目前处于孵化器阶段。
结构化并发将不同线程中运行的多个任务视为单个工作单元,从而简化错误处理、提高可靠性并增强可观察性。也就是说,结构化并发保留了单线程代码的可读性、可维护性和可观察性。
结构化并发的基本 API 是StructuredTaskScopeopen in new window。StructuredTaskScope 支持将任务拆分为多个并发子任务,在它们自己的线程中执行,并且子任务必须在主任务继续之前完成。
try (var scope = new StructuredTaskScope<Object>()) {
// 使用fork方法派生线程来执行子任务
Future<Integer> future1 = scope.fork(task1);
Future<String> future2 = scope.fork(task2);
// 等待线程完成
scope.join();
// 结果的处理可能包括处理或重新抛出异常
... process results/exceptions ...
} // close
结构化并发非常适合虚拟线程,虚拟线程是 JDK 实现的轻量级线程。许多虚拟线程共享同一个操作系统线程,从而允许非常多的虚拟线程。
13.Java 20 特性
1.作用域值(第一次孵化)
作用域值(Scoped Values)它可以在线程内和线程间共享不可变的数据,优于线程局部变量,尤其是在使用大量虚拟线程时。
final static ScopedValue<...> V = new ScopedValue<>();
// In some method
ScopedValue.where(V, <value>)
.run(() -> { ... V.get() ... call methods ... });
// In a method called directly or indirectly from the lambda expression
... V.get() ...
作用域值允许在大型程序中的组件之间安全有效地共享数据,而无需求助于方法参数。
14.Java 21 特性
作为LTS版本,JDK 21将获得5年首要支持和直到2031年9月的延长支持。当前的LTS版本是在2021年9月发布的JDK 17。非LTS版本(如JDK 20和JDK 19)仅获得6个月的首要支持,没有延长支持。LTS版本每两年发布一次。
1.结构化并发(预览)
通过结构化并发API简化了并发编程,将运行在不同线程中的相关任务组视为一个工作单元。这简化了错误处理和取消,提高了可靠性并增强了可观察性。
结构化并发在JDK 20和JDK 19(2022年3月、9月发布)中进行孵化;它将以预览API形式出现在JUC包。这次唯一显著的变化是StructuredTaskScope::Fork(...)方法返回一个[Subtask]而不是Future。
结构化并发可以使多线程编程更容易、更可靠。在传统的多线程编程中,线程的启动、执行和终止都是由开发人员手动管理的,因此容易出现线程泄漏、死锁和异常处理不当等问题。
使用结构化并发,开发人员可以更自然地组织并发任务,使任务之间的依赖关系更清晰,代码逻辑更简洁。结构化并发还提供了一些异常处理机制,以便更好地管理并发任务中的异常,避免异常导致的程序崩溃或数据不一致。
此外,结构化并发还可以通过限制并发任务的数量和优先级来防止资源竞争和短缺。这些特性使开发人员更容易实现高效可靠的并发程序,而无需过多关注底层线程管理。
使用方法:
想想下面的场景。假设您有三个任务要同时执行。只要其中任何一个任务完成并返回结果,就可以直接使用该结果,而其他两个任务可以停止。例如,天气服务通过三个通道获取天气条件,只要有一个通道返回它。
当然,在这种情况下,在Java 8下应该做的事情也是可能的。
List<Future<String>> futures = executor.invokeAll(tasks);
String result = executor.invokeAny(tasks);
使用ExecutorService的invokeAll和invokeAny实现,但是会有一些额外的工作。获得第一个结果后,需要手动关闭另一个线程。
在JDK21中,它可以通过结构化编程实现。
ShutdownOnSuccess捕获第一个结果并关闭任务范围以中断未完成的线程并唤醒调用线程。
在这种情况下,任何子任务的结果都可以直接获得,而无需等待其他未完成任务的结果。
它定义了获取第一个结果或在所有子任务失败时抛出异常的方法:
public static void main(String[] args) throws IOException {
try (var scope = new StructuredTaskScope.ShutdownOnSuccess<String>()) {
Future<String> res1 = scope.fork(() -> runTask(1));
Future<String> res2 = scope.fork(() -> runTask(2));
Future<String> res3 = scope.fork(() -> runTask(3));
scope.join();
System.out.println("scope:" + scope.result());
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
}
public static String runTask(int i) throws InterruptedException {
Thread.sleep(1000);
long l = new Random().nextLong();
String s = String.valueOf(l);
System.out.println(i + "task:" + s);
return s;
}
ShutdownOnFailure
执行多个任务,只要其中一个失败(发生异常或抛出其他活动异常),停止其他未完成的任务,并使用作用域。未能捕获并抛出异常。
如果所有任务都没问题,则使用Feature.get()或*Feature.resultnow()来获取结果。
public static void main(String[] args) throws IOException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<String> res1 = scope.fork(() -> runTaskWithException(1));
Future<String> res2 = scope.fork(() -> runTaskWithException(2));
Future<String> res3 = scope.fork(() -> runTaskWithException(3));
scope.join();
scope.throwIfFailed(Exception::new);
String s = res1.resultNow();
System.out.println(s);
String result = Stream.of(res1, res2,res3)
.map(Future::resultNow)
.collect(Collectors.joining());
System.out.println("result:" + result);
} catch (Exception e) {
e.printStackTrace();
}
}
public static String runTaskWithException(int i) throws InterruptedException {
Thread.sleep(1000);
long l = new Random().nextLong(3);
if (l == 0) {
throw new InterruptedException();
}
String s = String.valueOf(l);
System.out.println(i + "task:" + s);
return s;
}
2.作用域值
也处于预览状态,将使线程内外能够共享不可变数据。与线程本地变量相比,它们是首选,尤其是在使用大量虚拟线程时。
线程本地变量具有设计缺陷,包括不受约束的可变性、无边界的生命周期和昂贵的继承。作用域值允许庞大程序中的组件安全地共享数据,而无需使用方法参数。该提案在JDK 20中进行了孵化。该计划的目标包括易用性、可理解性、健壮性和性能。
3.禁止动态加载代理
一项准备禁止动态加载代理的提议要求,在向运行中的JVM动态加载代理时发出警告。这些警告旨在为将来的版本做准备,以默认禁止动态加载代理,以提高完整性。该提案的其他目标包括重新评估:
-
-
- 可维护性(涉及对运行代码的特殊修改)
- 完整性(假设运行代码不会任意更改)
-
之间的平衡,并确保大多数不需要动态加载代理的工具不受影响。该计划还要求将动态加载代理的能力与“超能力”功能(如深度反射)保持一致。代理是一种组件,可在应用程序运行时改变应用程序代码。这些组件由2004年JDK 5中的Java平台分析架构引入,作为一种让工具(特别是分析器)检测类的方法。尽管代理是为良性检测设计的,但高级开发人员发现了一些用例,如面向切面编程可以任意方式改变应用程序行为。同样,没有什么能阻止代理改变JDK本身等代码。JDK 5要求代理在命令行中指定,以确保应用程序所有者批准使用代理。在JDK 21中,计划要求像启动时加载代理一样,需要应用程序所有者批准动态加载代理。这一变化将Java平台进一步接近默认完整性。
4.密钥封装机制API
一种通过公钥密码学安全封装对称密钥的加密技术。该提案的一个目标是让应用程序能够使用:
-
-
- RSA密钥封装机制(RSA-KEM)
- 椭圆曲线集成加密方案(ECIES)等KEM算法
- 及美国国家标准与技术研究院(NIST)后量子密码学标准化过程的候选算法
-
另一个目标是能在更高级别的协议(如传输层安全协议TLS)和密码方案(如混合公钥加密HPKE)中使用KEM。安全提供者可以用Java或本机代码实现KEM算法,并包含RFC 9180中定义的Diffie-Hellman KEM(DHKEM)的实现。
5.弃用Windows 32位x86端口
以便在未来版本中删除,目的是在未来的版本中删除该端口。该提案旨在更新构建系统,以便尝试为Windows 32位x86配置构建时发出错误消息。消息可以通过新的配置选项抑制。此外,该计划是将端口及相关的端口特定功能标记为已弃用以删除相关文档。该提案指出,支持32位操作的最后一个Windows操作系统Windows 10将于2025年10月结束生命周期。
6.无名类和实例main方法预览
旨在使Java语言进化,以便学生在无需理解面向大型程序设计的语言特性的情况下编写第一个Java程序。学生可以为单类程序编写简化的声明,然后无缝地扩展程序以使用更高级的特性,而不是使用Java的单独方言。该提案不仅可以为Java提供平稳的入门,而且可以减少编写简单Java程序(如脚本和命令行实用程序)的麻烦。
7.无名模式和变量的预览
未命名模式匹配记录组件,而不声明组件的名称或类型,未命名变量可以初始化但不使用。两者都用下划线字符_表示。该提案旨在通过省略不必要的嵌套模式来改善记录模式的可读性,并通过识别必须声明但不会使用的变量来改善所有代码的可维护性。
8.分代ZGC
旨在通过为年轻对象和旧对象维护独立的代来改善应用程序性能。年轻对象往往很快就会死亡;维护独立的代将允许ZGC更频繁地收集年轻对象。运行在分代ZGC上的应用程序应会看到以下好处:分配中断风险更低,需要的堆内存开销更低,垃圾收集CPU开销更低。这些好处应该可以在吞吐量不明显下降的情况下实现。
这是一种新的垃圾收集器,旨在提高大型堆内存的性能和可扩展性。分代 ZGC 利用分代内存布局和分代收集算法,可将垃圾收集的负担分散到不同的代中,从而减少垃圾收集的停顿时间。
示例:
# 启用分代 ZGC (defaults to non-generational) $ java -XX:+UseZGC # 启用非分代 Generational ZGC $ java -XX:+UseZGC -XX:+ZGenerational
分代 ZGC 可以显著减少垃圾回收过程中的停顿时间,并提高应用程序的响应性能。这对于大型 Java 应用程序和高并发场景下的性能优化非常有价值。
在未来的版本中,官方打算将分代 ZGC 作为默认值,此时 -XX:-ZGenerational 将选择非分代 ZGC。在更晚的版本中,打算删除非分代 ZGC,届时 ZGenerational 选项将变得过时。
使用方式:
1.全局使用:
在idea的bin目录下找到 idea64.exe.vmoptions 并编辑,增加以下配置:
# 如果旧版 可能需要开启实验室功能
-XX:+UnlockExperimentalVMOptions
-XX:+UseZGC
完善的一些完成IDEA配置,这个也做了关于IDEA的硬件加速,内存调整:
-server
-Xms2048m
-Xmx2048m
-XX:ReservedCodeCacheSize=512m
#-XX:+UseG1GC
# 如果旧版 可能需要开启实验室功能
#-XX:+UnlockExperimentalVMOptions
-XX:+UseZGC
-XX:SoftRefLRUPolicyMSPerMB=50
-XX:CICompilerCount=2
-XX:+HeapDumpOnOutOfMemoryError
-XX:-OmitStackTraceInFastThrow
-XX:+IgnoreUnrecognizedVMOptions
-XX:CompileCommand=exclude,com/intellij/openapi/vfs/impl/FilePartNodeRoot,trieDescend
-ea
-Dsun.io.useCanonCaches=false
-Dsun.java2d.metal=true
# update
-Dsun.java2d.opengl=true
-Djbr.catch.SIGABRT=true
-Djdk.http.auth.tunneling.disabledSchemes=""
-Djdk.attach.allowAttachSelf=true
-Djdk.module.illegalAccess.silent=true
-Dkotlinx.coroutines.debug=off
2.单应用使用:
编辑运行配置,添加 vm 启动参数:
-XX:+UseZGC
9.记录模式
在JDK 19和JDK 20中进行了预览,用于解构记录值。记录模式和类型模式可以嵌套,以启用强大的、声明性的和可组合的数据导航和处理形式。该提案的目标包括扩展模式匹配以解构记录类的实例,添加嵌套模式以启用更可组合的数据查询。此特性与switch表达式和语句的模式匹配(见下文)共同演化。当前的JEP将在继续的经验和反馈的基础上通过进一步完善来最终确定该特性。除了少量编辑变化外,主要变化是删除了记录模式在增强的for语句标题中出现的支持。该特性可能会在未来的JEP中重新提出。
记录模式是一种新的语言特性,允许我们定义简洁且不可变的数据模型。它通过自动创建构造函数、getter 和 equals/hashCode 等方法来简化数据对象的定义。
示例:
public record Person(String name, int age) {}
Person person = new Person("Alice", 25);
System.out.println(person.name());
System.out.println(person.age());
在上面的代码中,我们使用记录模式定义了一个名为
Person
的数据对象,并创建了一个实例。我们可以使用自动生成的getter方法轻松访问记录对象的属性。记录模式简化了数据对象的定义和使用,减少了样板代码,提高了代码的可读性。
10.switch的模式匹配
使得switch表达式或语句可以针对许多模式进行测试,每个模式都有特定的操作,这样复杂的数据查询可以安全简洁地表达。该特性最初在JDK 17中提出,之后在JDK 18、JDK 19和JDK 20中进行了完善。它将在JDK 21中通过进一步的优化最终确定。与前面的JEP相比,主要变化是删除了带括号的模式并允许合格的enum常量,如带switch表达式和语句的常量。目标包括通过允许模式出现在case标签中来扩展switch表达式和语句的表达能力和适用范围,允许switch的历史 null敌意在需要时得到放松,并通过要求模式switch语句覆盖所有潜在的输入值来增加switch语句的安全性。另一个目标是确保现有的switch表达式和语句继续编译而不变,并具有相同的语义。
在以往的switch语句中,对于case中的类型匹配限制是很多的。比如下面这个例子中的Map中可能存储了不同类型的对象,我们要判断的时候,就只能依靠if-else来完成。
Map<String, Object> data = new HashMap<>();
data.put("key1", "aaa");
data.put("key2", 111);
if (data.get("key1") instanceof String s) {
log.info(s);
}
if (data.get("key") instanceof String s) {
log.info(s);
} else if (data.get("key") instanceof Double s) {
log.info(s);
} else if (data.get("key") instanceof Integer s) {
log.info(s);
}
现在可以这样:
switch (data.get("key1")) {
case String s -> log.info(s);
case Double d -> log.info(d.toString());
case Integer i -> log.info(i.toString());
default -> log.info("");
}
11.向量API的第六个孵化器
该API表达了在支持的CPU体系结构上可靠编译为优化向量指令的向量计算,其性能优于等效的标量计算。向量API此前在JDK 16至JDK 20中进行了孵化。这个最新版本包括性能增强和错误修复。该提案的目标包括清晰简洁,与平台无关,并在x64和AArch64体系结构上提供可靠的运行时编译和性能。其他目标包括在向量计算无法完全表示为向量指令序列时优雅降级。
12.外部函数和内存API的第三次预览
它使Java程序能够与Java运行时之外的代码和数据进行互操作。通过高效调用外部函数和安全访问外部内存,该API使Java程序能够调用本机库并处理本机数据,而无需使用JNI(Java本机接口)的脆弱性和危险性。该API此前在JDK 20和JDK 19中进行了预览。JDK 21预览中的改进包括带有新的元素来解引用地址布局的增强布局路径、Arena接口中的本地段生命周期的集中管理、备用本机链接器实现以及VaList的删除。该提案的目标包括易用性、性能、通用性和安全性。它的目标不是在此API之上重新实现JNI,也不是以任何方式更改JNI。
13.虚拟线程
是轻量级线程,它承诺大大减少编写、维护和观察高吞吐量并发应用程序的工作量。该计划的目标包括使按线程请求风格编写的服务器应用程序能够在接近最佳硬件利用率的情况下扩展,使使用lang.Thread API的现有代码通过最小更改采用虚拟线程,并使用当前JDK工具轻松调试和分析虚拟线程。虚拟线程在JDK 20和JDK 19中进行了预览,将在JDK 21中最终确定。在JDK 21中,虚拟线程现在始终支持线程本地变量,并使创建不具有这些变量的虚拟线程成为不可能。保证对线程本地变量的支持可以确保更多现有库可以不变地与虚拟线程一起使用,并帮助将面向任务的代码迁移到使用虚拟线程。
历史版本中,JDK 中的每个 java.lang.Thread 实例都是一个平台线程。平台线程在底层操作系统线程上运行 Java 代码,并在代码的整个生命周期内捕获操作系统线程。平台线程的数量受限于操作系统线程的数量。
在 JDK 21 中引入的 Fibers(协程)被设计成一种轻量级的线程模型,它可以更高效地执行异步代码,避免了传统线程模型中线程的创建和销毁开销,提供更高的并发性和更低的资源消耗。
使用Fibers进行异步操作简单示例:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});} // executor.close() is called implicitly, and waits
通过在新的虚拟线程池中执行异步操作,我们可以以更高效的方式处理并发任务。
一般来说,虚拟线程是一种新的线程类型,它可以提高应用程序的性能和资源利用率,同时还可以使用传统的线程相关api。虚拟线程与协程有许多相似之处,但也有一些不同之处。
Virtual Threads确实可以使多线程编程更容易、更高效。与传统的操作系统线程相比,创建和销毁虚拟线程的开销更小,线程上下文切换的开销也更小,因此可以大大降低多线程编程中的资源消耗和性能瓶颈。
使用Virtual Threads,开发人员可以像编写传统线程代码一样编写代码,而不必担心线程的数量和调度,因为JVM将自动管理虚拟线程的数量和调度。此外,虚拟线程还支持传统的线程相关api,如ThreadLocal、Lock和Semaphore,这使得开发人员更容易将传统线程代码迁移到虚拟线程中。
虚拟线程的引入使多线程编程更高效、更简单、更安全,允许开发人员更多地关注业务逻辑,而不必过多关注底层线程管理。
使用虚拟线程:
首先,声明一个线程类,实现from Runnable,并实现run方法:
public class SimpleThread implements Runnable{
@Override
public void run() {
System.out.println("name:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
然后,使用这个线程类并启动线程:
Thread thread = new Thread(new SimpleThread());
thread.start();
拥有虚拟线程后,如何实现它?
Thread.ofPlatform().name("thread-test").start(new SimpleThread());
下面是使用虚拟线程的几种方法:
1.直接启动一个虚拟线程
Thread thread = Thread.startVirtualThread(new SimpleThread());
2.使用ofVirtual(),构建器模式启动虚拟线程,您可以设置线程名称,优先级,异常处理和其他配置
Thread.ofVirtual()
.name("thread-test")
.start(new SimpleThread());
// Or
Thread thread = Thread.ofVirtual()
.name("thread-test")
.uncaughtExceptionHandler((t, e) -> {
System.out.println(t.getName() + e.getMessage());
}).unstarted(new SimpleThread());
thread.start();
3. 使用Factory创建线程
ThreadFactory factory = Thread.ofVirtual().factory();
Thread thread = factory.newThread(new SimpleThread());
thread.setName("thread-test");
thread.start();
4. 使用Executor
ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor();
Future<?> submit = executorService.submit(new SimpleThread());
Object o = submit.get();
14.序列集合
提案引入了表示具有定义遭遇顺序的集合的接口。每个集合都有明确定义的第一个和第二个元素等,直到最后一个元素。提供了统一的API来接受第一个和最后一个元素并以相反顺序处理元素。该提案的动机是Java的集合框架缺少表示具有定义遭遇顺序的元素序列的集合类型。它还缺少适用于这些集合的统一操作集。这些缺陷一直是个问题和抱怨的来源。该提案要求为序列集合、集合和映射定义接口,并将这些接口 retrofit 到现有的集合类型层次结构中。所有这些新方法都有默认实现。
序列化集合通过提供可预测的迭代顺序,解决了在多线程环境下遍历集合时可能出现的竞争条件和不确定性问题。
示例:
List<String> list = new SequencedArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Orange");
for (String fruit : list.reversed()) {
System.out.println(fruit);
}
在上面的代码中,我们创建了一个
SequencedArrayList
,并向其中添加了一些水果。使用增强的 for
反向循环遍历集合时,我们可以确保按照添加的顺序输出水果的名称:Orange,Banana,Apple
。这种可预测的顺序确保了集合在多线程环境下的一致性和可靠性。
15.字符串模板
JDK 21中的预览功能,它通过将字面文本与嵌入式表达式和处理器结合来补充Java的现有字符串文本块,以产生特定结果。这种语言特性和API旨在通过使动态计算的值易于表示字符串来简化Java程序的编写。它有望增强表达式的可读性,提高程序安全性,保持灵活性,并简化使用以非Java语言编写的字符串的API。启用从字面文本和嵌入式表达式组合派生非字符串表达式的开发也是一个目标。
字符串模板是JDK 21中引入的一项预览功能。它可以大大简化Java中处理复杂字符串的过程。在以往的Java中,我们通常使用字符串拼接或格式化方法来构建字符串,而这些方法可能会变得冗长和难以维护。使用字符串模板,我们可以在字符串中直接嵌入表达式,而无需进行额外的处理。
示例:
String name = "Joan";
String info = STR."My name is \{name}";
assert info.equals("My name is Joan"); // true
在上面的代码中,我们使用
${}
将变量插入到字符串中。在运行时,这些表达式将被相应的变量值替换。这使得构建动态字符串变得更加简单和直观,尤其是对于需要频繁拼接字符串的场景。
文章评论