目录

一、Java17新特性

1、switch语法的变化(预览)

2、Sealed Classes

3、伪随机数的变化

4、去除了AOT和JIT

二、Java18新特性

1、默认使用UTF-8字符编码

2、简单的web服务器

3、将被移除的方法

4、@snippet注解

三、Java19新特性

1、Virtual Threads(Preview)虚拟线程

四、Java20新特性


一、Java17新特性

        Java17是一个LTS(long term support)长期支持的版本,根据计划来看会支持到2029年(java8会支持到2030年),同时Oracle提议下一个LTS版本是java21,在2023年9月发布,这样将LTS版本的发布周期由之前的3年变为了2年。

Java17其他特性

Oracle is collaborating with the Java developer community and the JCP on enhancing LTS 
scheduling to give organizations more flexibility on when, or if, they want to migrate to
 a newer Java LTS version. Oracle is proposing that the next LTS release should be Java 21
 and made available in September 2023, which will change the ongoing LTS release cadence 
from three years to two years.

Oracle正在与Java开发人员社区和JCP合作,增强LTS调度,以使组织在何时或是否希望迁移到较新的Java LTS
版本方面具有更大的灵活性。Oracle建议下一个LTS版本应该是Java 21,并于2023年9月发布,这将把正在进行
的LTS发布周期从三年改为两年。

Java版本支持时间

oracle官网

Java17-20新特性-LMLPHP

1、switch语法的变化(预览)

        在之前版本中新增的instanceof模式匹配的特性在switch中也支持了,即我们可以在switch中减少强转的操作。

Rabbit和Bird均实现了Animal接口

package com.lwz.java.v17;

public interface Animal {
}

class Rabbit implements Animal {
    public void run() {
        System.out.println("run");
    }
}

class Bird implements Animal {
    public void fly() {
        System.out.println("fly");
    }
}

测试

package com.lwz.java.v17;

public class Switch01 {
    public static void main(String[] args) {
        Animal a=new Rabbit();
        animalEat(a);//run
    }

    public static void animalEat(Animal a){
        switch (a){
            case Rabbit r -> r.run();//判断a是否是Rabbit类型,若是则强转之后赋值给r
            case Bird b ->b.fly();
            case null -> System.out.println("null");
            default -> System.out.println("no animal");
        }
    }
}

2、Sealed Classes

        jdk15中已经添加了Sealed Classes,只不过当时是作为预览版,经历了2个版本之后,在jdk17中Sealed Classes已经成为正式版了,Sealed Classes的作用是可以限制一个类或者接口可以由哪些子类继承或者实现。

3、伪随机数的变化

增加了伪随机数相关的类和接口来让开发者使用stream流进行操作

RandomGenerator
RandomGeneratorFactory

之前的java.util.Random和java.util.concurrent.ThreadLocalRandom都是RandomGenerator接口的实现类。

4、去除了AOT和JIT

AOT(Ahead-of-Time)是java9中新增的功能,可以先将应用中的字节码编译成机器码。

Graal编译器作为使用java开发的JIT(just-in-time)即时编译期在java10中加入

二、Java18新特性

java新特性

1、默认使用UTF-8字符编码

从jdk18开始,默认使用UTF-8字符编码。我们可以通过如下参数修改为其他字符编码:

-Dfile.encoding=UTF-8

2、简单的web服务器

可以通过jwebserver命令启动jdk18中提供的静态web服务器,可以利用该工具查看一些原型,做简单的测试。在命令提示符中输入jwebserver命令后会启动,然后在浏览器中输入:http://127.0.0.1:8000/即可看到当前命令提示符路径下的文件了。

D:\.m2>jwebserver
默认情况下绑定到环回。如果要表示所有接口,请使用 "-b 0.0.0.0" 或 "-b ::"。
为 127.0.0.1 端口 8000 上的 D:\.m2 及子目录提供服务
URL http://127.0.0.1:8000/

Java17-20新特性-LMLPHP

若cmd执行jwebserver出现中文乱码解决方法:
设定cmd的编码为utf-8
打开cmd,输入以下命令

chcp 65001

这样既可以更改cmd的编码为UTF-8了。
以下是常用的cmd编码

-- 运行jar解决中文乱码
java -Dfile.encoding=utf-8 -jar project.jar

3、将被移除的方法

在jdk18中标记了Object中的finalize方法,Thread中的stop方法将在未来被移除

    @Deprecated(since="1.2", forRemoval=true)
    public final void stop() {
        throw new UnsupportedOperationException();
    }

4、@snippet注解

以前在文档注解中编写代码时需要添加code标签,使用较为不便,通过@snippet注解可以更方便的将文档注释中的代码展示在api文档中

package com.lwz.java.v17;

/**
 * 代码注释
 * {@snippet :
 * System.out.println();
 * }
 */
public class SnippetTest {
    public static void main(String[] args) {
        System.out.println();
    }
}

使用javadoc生成注释文件

D:\...java\com\lwz\java\v17>javadoc -d myfile SnippetTest.java
正在加载源文件SnippetTest.java...
正在构造 Javadoc 信息...
正在创建目标目录: "myfile\"
正在构建所有程序包和类的索引...
标准 Doclet 版本 21.0.1+12-LTS-29
正在构建所有程序包和类的树...
正在生成myfile\com\lwz\java\v17\SnippetTest.html...
SnippetTest.java:9: 警告: 使用不提供注释的默认构造器
public class SnippetTest {
       ^
SnippetTest.java:10: 警告: 没有注释
    public static void main(String[] args) {
                       ^
正在生成myfile\com\lwz\java\v17\package-summary.html...
正在生成myfile\com\lwz\java\v17\package-tree.html...
正在生成myfile\overview-tree.html...
正在构建所有类的索引...
正在生成myfile\allclasses-index.html...
正在生成myfile\allpackages-index.html...
正在生成myfile\index-all.html...
正在生成myfile\search.html...
正在生成myfile\index.html...
正在生成myfile\help-doc.html...
2 个警告

会生成myfile文件夹,双击index.html文件,效果如下

Java17-20新特性-LMLPHP

三、Java19新特性

1、Virtual Threads(Preview)虚拟线程

        该特性在java19中是预览版,虚拟线程是一种用户态下的线程,类似go语言中的goroutines和Erlang中的processes,虚拟线程并非比线程快,而是提高了应用的吞吐量,相比于传统的线程是由操作系统调度来看,虚拟线程是我们自己程序调度的线程。如果你对之前java提供的线程API比较熟悉了,那么在学习虚拟线程的时候比较轻松,传统线程能运行的代码,虚拟线程也可以运行。虚拟线程的出现,并没有修改java原有的并发模型,也不会替代原有的线程。虚拟线程主要作用是提升服务器端的吞吐量。

吞吐量的瓶颈

服务器应用程序的伸缩性受利特尔法则(Little's Law)的制约,与下面3点有关

1、延迟:请求处理的耗时

2、并发慢:同一时刻处理的请求数量

3、吞吐量:单位时间内处理的数据数量

比如一个服务器应用程序的延迟是50ms,处理10个并发请求,则吞吐量是200请求/秒(10/0.05),如果吞吐量要达到2000请求/秒,则处理的并发请求数量是100.按照1个请求一个线程的比例来看,要想提高吞吐量,线程数量也要增加。

java中的线程是在操作系统线程(OS thread)进行了一层包装,而操作系统中线程是重量级资源,在硬件配置确定的情况下,我们就不能创建更多的线程了,此时线程数量就限制了系统性能,为了解决该问题,虚拟线程就出现了。

与虚拟地址可以映射到物理内存类似,java是将大量的虚拟线程映射到少量的操作系统线程,多个虚拟线程可以使用同一个操作系统线程,其创建所耗费的资源也是极其低廉的,无需系统调用和系统级别的上下文切换,且虚拟线程的生命周期短暂,不会有很深的栈的调用,一个虚拟线程的生命周期中只运行一个任务,因此我们可以创建大量的虚拟线程,且虚拟线程无需池化。

虚拟线程的应用场景

在服务器端的应用程序中,可能会有大量的并发任务需要执行,而虚拟线程能够明显的提高应用的吞吐量。下面的场景能够显著的提高程序的吞吐量:

  • 至少几千的并发任务量
  • 任务为io密集型

下面代码为每个任务创建一个线程,当任务量较多的时候,你的电脑可以感受到明显的卡顿(如果没有可以增加任务数量试下)

import java.time.Duration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

public class ThreadTest {
    public static void main(String[] args) {
        //ExecutorService实现了AutoCloseable接口,可以自动关闭了
        try(ExecutorService executor= Executors.newCachedThreadPool()){
            //向executor中提交1000000个任务
            IntStream.range(0,1000000).forEach(i->{
                executor.submit(()->{
                    try{
                        Thread.sleep(Duration.ofSeconds(1));
                        System.out.println("执行任务:"+i);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                });
            });
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

虚拟线程演示

import java.time.Duration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.IntStream;

public class VirtualThread01 {
    public static void main(String[] args) {
        //ExecutorService实现了AutoCloseable接口,可以自动关闭了
        try(ExecutorService executor= Executors.newVirtualThreadPerTaskExecutor()){
            //向executor中提交1000000个任务
            IntStream.range(0,1000000).forEach(i->{
                executor.submit(()->{
                    try{
                        Thread.sleep(Duration.ofSeconds(1));
                        System.out.println("执行任务:"+i);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                });
            });
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

平台线程和虚拟线程

平台线程(platform thread):指java中的线程,比如通过Executors.newFixedThreadPool()创建出来的线程,我们称之为平台线程。

虚拟线程并不会直接分配给cpu去执行,而是通过调度器分配给平台线程,平台线程再被调度器管理。java中虚拟线程的调度器采用了工作窃取的模式进行FIFO的操作,调度器的并行数默认是jvm获取的处理器数量(通过该方法获取的数量Runtime.getRuntime().availableProcessors()),调度器并非分时(time sharing)的,在使用虚拟线程编写程序时,不能控制虚拟线程何时分配给平台线程,也不能控制平台线程何时分配给cpu。

以前任务和平台的关系

Java17-20新特性-LMLPHP

使用虚拟线程后,任务-虚拟线程-调度器-平台线程的关系,1个平台线程可以被调度器分配不同的虚拟线程

Java17-20新特性-LMLPHP

携带器

        调度器将虚拟线程挂载到平台线程之后,该平台线程叫做虚拟线程的携带器,调度器并不维护虚拟线程和携带器之间的关联关系,因此在一个虚拟线程的生命周期中可以被分配到不同的携带器,即虚拟线程运行了一小段代码后,可能会脱离携带器,此时其他的虚拟线程会被分配到这个携带器上。

携带器和虚拟线程是相互独立的,比如:

  • 虚拟线程不能使用携带器的标识,Thread.current()方法获取的是虚拟线程本身。
  • 两者有各自的栈空间
  • 两者不能访问对方的Thread Local变量

在程序的执行过程中,虚拟线程遇到阻塞的操作时大部分情况下会被解除挂载,阻塞结束后,虚拟线程会被调度器重新挂载到携带器上,因此虚拟线程会频繁的挂载和解除挂载,这并不会导致操作系统线程的阻塞。

有些阻塞操作并不会导致虚拟线程的解除挂载,这样会同时阻塞携带器和操作系统线程,例如:操作系统基本的文件操作,java中的Object.wait()方法。下面两种情况不会导致虚拟线程的解除挂载:

  1. 执行synchronized同步代码(会导致携带器阻塞,所以建议使用ReentrantLock替换掉synchronized)
  2. 执行本地方法或外部函数

虚拟线程和平台线程api的区别

从内存空间上来说,虚拟线程的栈空间可以看作是一个大块的栈对象,它被存储在了java堆中,相比于单独存储对象,堆中存储虚拟线程的栈会造成一些空间的浪费,这点在后续的java版本中应该会得到改善,当然这样也是有一些好处的,就是可以重复利用这部分栈空间,不用多次申请开辟新的内存地址。虚拟线程的栈空间最大可以达到平台线程的栈空间容量。

虚拟线程并不是GC root,其中的引用不会出现stop-world,当虚拟线程被阻塞之后,比如BlockingQueue.take(),平台线程既不能获取到虚拟线程,也不能获取到queue队列,这样该平台线程可能会被回收掉,虚拟线程在运行或阻塞时不会被GC。

  • 通过Thread构造方法创建的线程都是平台线程
  • 虚拟线程是守护线程,不能通过setDaemon方法改成非守护线程
  • 虚拟线程的优先级是默认的5,不能被修改,将来的版本可能允许修改
  • 虚拟线程不支持stop(),suspend(),resume()方法

创建虚拟线程的方式

java中创建虚拟线程本质都是通过Thread.Builder.OfVirtual对象进行创建的

创建虚拟线程的三种方式:

  1. 通过Thread.startVirtualThread直接创建一个虚拟线程
  2. Thread vt1=Thread.ofVirtual().name("虚拟线程1").started(task2);//启动虚拟线程
  3. Executors.newVirtualThreadPerTaskExecutor()
import java.time.Duration;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 * 创建虚拟线程的方式
 */
public class VirtualThread02 {
    public static void main(String[] args) throws InterruptedException {
        //方式1
        Runnable task1=()->{
            System.out.println("执行任务");
        };
        //创建虚拟线程并运行
        Thread.startVirtualThread(task1);

        //主线程睡眠
        TimeUnit.SECONDS.sleep(1);

        //方式2
        Runnable task2=()->{
            System.out.println(Thread.currentThread().getName());
        };

        Thread vt1=Thread.ofVirtual().name("虚拟线程1").unstarted(task2);
        vt1.start();//启动虚拟线程
        System.out.println(vt1.isVirtual());
        //主线程睡眠
        TimeUnit.SECONDS.sleep(1);
        //方式3
        //ExecutorService实现了AutoCloseable接口,可以自动关闭了
        try(ExecutorService executor= Executors.newVirtualThreadPerTaskExecutor()){
            //向executor中提交1000000个任务
            IntStream.range(0,1000000).forEach(i->{
                executor.submit(()->{
                    try{
                        Thread.sleep(Duration.ofSeconds(1));
                        System.out.println("执行任务:"+i);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                });
            });
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

Thread.Builder接口

在jdk19新增了一个密封(sealed)接口Builder,该接口只允许有两个子接口:

public sealed interface Builder
            permits Builder.OfPlatform, Builder.OfVirtual {

上面创建虚拟线程的方式本质都是通过OfVirtual 来进行创建的,OfVirtual和OfPlatform接口中的api很多都是相同的,OfPlatform中方法更多,所以下面以OfPlatform来演示使用方式。

public class VirtualThread03 {
    public static void main(String[] args) {
        Runnable task=()->{
            System.out.println(Thread.currentThread().getName());
        };
        //Thread.ofPlatform()优点可以链式调用
        Thread t=Thread.ofPlatform().name("线程1").start(task);
        ThreadFactory factory = Thread.ofPlatform().factory();
        factory.newThread(task).start();
    }
}

四、Java20新特性

java20中没有太大的变化。

Java14-16新特性

一个程序员最重要的能力是:写出高质量的代码!!
有道无术,术尚可求也,有术无道,止于术。
无论你是年轻还是年长,所有程序员都需要记住:时刻努力学习新技术,否则就会被时代抛弃!

11-05 00:56