赞
踩
目录
1、Virtual Threads(Preview)虚拟线程
Java17是一个LTS(long term support)长期支持的版本,根据计划来看会支持到2029年(java8会支持到2030年),同时Oracle提议下一个LTS版本是java21,在2023年9月发布,这样将LTS版本的发布周期由之前的3年变为了2年。
- 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版本支持时间
在之前版本中新增的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");
- }
- }
- }
jdk15中已经添加了Sealed Classes,只不过当时是作为预览版,经历了2个版本之后,在jdk17中Sealed Classes已经成为正式版了,Sealed Classes的作用是可以限制一个类或者接口可以由哪些子类继承或者实现。
增加了伪随机数相关的类和接口来让开发者使用stream流进行操作
- RandomGenerator
- RandomGeneratorFactory
之前的java.util.Random和java.util.concurrent.ThreadLocalRandom都是RandomGenerator接口的实现类。
AOT(Ahead-of-Time)是java9中新增的功能,可以先将应用中的字节码编译成机器码。
Graal编译器作为使用java开发的JIT(just-in-time)即时编译期在java10中加入
从jdk18开始,默认使用UTF-8字符编码。我们可以通过如下参数修改为其他字符编码:
-Dfile.encoding=UTF-8
可以通过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/
若cmd执行jwebserver出现中文乱码解决方法:
设定cmd的编码为utf-8
打开cmd,输入以下命令
chcp 65001
这样既可以更改cmd的编码为UTF-8了。
以下是常用的cmd编码
字符编码 | 对应字符 |
GBK(默认) | 936 |
美国英语 | 437 |
utf-8 | 65001 |
- -- 运行jar解决中文乱码
- java -Dfile.encoding=utf-8 -jar project.jar
在jdk18中标记了Object中的finalize方法,Thread中的stop方法将在未来被移除
- @Deprecated(since="1.2", forRemoval=true)
- public final void stop() {
- throw new UnsupportedOperationException();
- }
以前在文档注解中编写代码时需要添加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文件,效果如下
该特性在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是将大量的虚拟线程映射到少量的操作系统线程,多个虚拟线程可以使用同一个操作系统线程,其创建所耗费的资源也是极其低廉的,无需系统调用和系统级别的上下文切换,且虚拟线程的生命周期短暂,不会有很深的栈的调用,一个虚拟线程的生命周期中只运行一个任务,因此我们可以创建大量的虚拟线程,且虚拟线程无需池化。
虚拟线程的应用场景
在服务器端的应用程序中,可能会有大量的并发任务需要执行,而虚拟线程能够明显的提高应用的吞吐量。下面的场景能够显著的提高程序的吞吐量:
下面代码为每个任务创建一个线程,当任务量较多的时候,你的电脑可以感受到明显的卡顿(如果没有可以增加任务数量试下)
- 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。
以前任务和平台的关系
使用虚拟线程后,任务-虚拟线程-调度器-平台线程的关系,1个平台线程可以被调度器分配不同的虚拟线程
携带器
调度器将虚拟线程挂载到平台线程之后,该平台线程叫做虚拟线程的携带器,调度器并不维护虚拟线程和携带器之间的关联关系,因此在一个虚拟线程的生命周期中可以被分配到不同的携带器,即虚拟线程运行了一小段代码后,可能会脱离携带器,此时其他的虚拟线程会被分配到这个携带器上。
携带器和虚拟线程是相互独立的,比如:
在程序的执行过程中,虚拟线程遇到阻塞的操作时大部分情况下会被解除挂载,阻塞结束后,虚拟线程会被调度器重新挂载到携带器上,因此虚拟线程会频繁的挂载和解除挂载,这并不会导致操作系统线程的阻塞。
有些阻塞操作并不会导致虚拟线程的解除挂载,这样会同时阻塞携带器和操作系统线程,例如:操作系统基本的文件操作,java中的Object.wait()方法。下面两种情况不会导致虚拟线程的解除挂载:
虚拟线程和平台线程api的区别
从内存空间上来说,虚拟线程的栈空间可以看作是一个大块的栈对象,它被存储在了java堆中,相比于单独存储对象,堆中存储虚拟线程的栈会造成一些空间的浪费,这点在后续的java版本中应该会得到改善,当然这样也是有一些好处的,就是可以重复利用这部分栈空间,不用多次申请开辟新的内存地址。虚拟线程的栈空间最大可以达到平台线程的栈空间容量。
虚拟线程并不是GC root,其中的引用不会出现stop-world,当虚拟线程被阻塞之后,比如BlockingQueue.take(),平台线程既不能获取到虚拟线程,也不能获取到queue队列,这样该平台线程可能会被回收掉,虚拟线程在运行或阻塞时不会被GC。
创建虚拟线程的方式
java中创建虚拟线程本质都是通过Thread.Builder.OfVirtual对象进行创建的
创建虚拟线程的三种方式:
- 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中没有太大的变化。
一个程序员最重要的能力是:写出高质量的代码!!
有道无术,术尚可求也,有术无道,止于术。
无论你是年轻还是年长,所有程序员都需要记住:时刻努力学习新技术,否则就会被时代抛弃!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。