自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

SpringBoot3使用虛擬線程一定要小心了

開發(fā) 前端
虛擬線程在項(xiàng)目中應(yīng)用時(shí)你稍不注意就可能出現(xiàn)問題。本篇文章將要講述的是在非Web應(yīng)用的情況下使用虛擬線程出現(xiàn)的問題(并非BUG)。

環(huán)境:SpringBoot3.2.5 + JDK21

1.簡介

SpringBoot從3.2.0-M1版本開始支持虛擬線程。虛擬線程是JDK 21版本正式發(fā)布的一個(gè)新特性,它與平臺(tái)線程的主要區(qū)別在于虛擬線程在運(yùn)行周期內(nèi)不依賴操作系統(tǒng)線程,而是與硬件脫鉤,因此被稱為“虛擬”。這種解耦是由JVM提供的抽象層賦予的,使得虛擬線程的運(yùn)行成本遠(yuǎn)低于平臺(tái)線程,并且可以消耗更少的內(nèi)存。因此,從SpringBoot 3.2.0-M1開始,通過使用虛擬線程,提升系統(tǒng)的整體性能。

虛擬線程在項(xiàng)目中應(yīng)用時(shí)你稍不注意就可能出現(xiàn)問題。本篇文章將要講述的是在非Web應(yīng)用的情況下使用虛擬線程出現(xiàn)的問題(并非BUG)。

2. 實(shí)戰(zhàn)案例

注意:本案例是非Web應(yīng)用。只要你不要引入spring-boot-starter-web模塊或者下面配置后都將以非web模式下運(yùn)行。

public static void main(String[] args) {
  new SpringApplicationBuilder()
    .sources(SpringbootNonWebApplication.class)
    // 即便引入了web模塊,但這里設(shè)置為非web應(yīng)用
    .web(WebApplicationType.NONE)
    .run(args) ;
}

非web應(yīng)用,啟動(dòng)容器后并不會(huì)啟動(dòng)嵌入式的web server,如果你當(dāng)前應(yīng)用中并沒有其它線程執(zhí)行(非守護(hù)線程),那么程序?qū)⒆詣?dòng)停止(啟動(dòng)即停止)。

圖片圖片

啟動(dòng)完后自動(dòng)停止。

2.1 啟動(dòng)定時(shí)任務(wù)

在一個(gè)非web環(huán)境下啟動(dòng)定時(shí)任務(wù):

@Component
public class TaskComponent {


  @Scheduled(fixedRate = 3000)
  public void task1() throws Exception {
    System.out.printf("當(dāng)前執(zhí)行線程: %s%n", Thread.currentThread()) ;
    // TODO 執(zhí)行任務(wù)
    TimeUnit.SECONDS.sleep(1) ;
  }
}

上面定義了每隔3s執(zhí)行的定時(shí)任務(wù)(記得通過@EnableScheduling注解開啟任務(wù)調(diào)用功能)。

啟動(dòng)服務(wù)

圖片圖片

程序規(guī)律的執(zhí)行,每隔3s輸出信息。

2.2 虛擬線程執(zhí)行任務(wù)

接下來開啟虛擬線程。

如果運(yùn)行的是 Java 21 或更高版本,可以通過配置如下屬性來啟用虛擬線程。

spring:
  threads:
    virtual:
      enabled: true

再次運(yùn)行程序

圖片圖片

根據(jù)打印信息,執(zhí)行線程確實(shí)是通過虛擬線程執(zhí)行,但是僅僅啟動(dòng)時(shí)輸出了一條信息,程序就終止了,這肯定不是我們想要的。什么原因呢?

2.3 守護(hù)線程

這是一段非常簡單的代碼了

Thread t = new Thread(() -> {
  try {
    System.out.println("start..." + System.currentTimeMillis()) ;
    TimeUnit.SECONDS.sleep(5) ;
  } catch (Exception e) {
    e.printStackTrace() ;
  }
  System.out.println(" over..." + System.currentTimeMillis()) ;
}) ;
t.start() ;

輸出結(jié)果:

start...1613150235234
 over...1613150240238

程序等待3s后終止。接下來將上面Thread線程做如下配置:

// 設(shè)置為守護(hù)線程
t.setDaemon(true) ;

再次執(zhí)行,這次執(zhí)行控制臺(tái)不會(huì)有任何的輸出程序就終止了。

在Java中當(dāng)所有非守護(hù)線程都執(zhí)行完以后,守護(hù)線程會(huì)自動(dòng)終止;守護(hù)線程一般用于執(zhí)行后臺(tái)任務(wù),資源清理等。

接下來通過虛擬線程執(zhí)行上面的代碼:

OfVirtual virtual = Thread.ofVirtual().name("Pack-") ;
Thread t = virtual.start(() -> {
  try {
    System.out.println("start..." + System.currentTimeMillis()) ;
    TimeUnit.SECONDS.sleep(5) ;
  } catch (Exception e) {
    e.printStackTrace() ;
  }
  System.out.println("over..." + System.currentTimeMillis()) ;
}) ;
TimeUnit.SECONDS.sleep(1) ;

等待1s后程序終止,只輸出如下結(jié)果:

start...1613840844449

虛擬線程難道也是守護(hù)線程?

通過如下代碼查看上面的虛擬線程是否是守護(hù)線程:

System.out.println(t.isDaemon()) ;

輸出結(jié)果:

true

既然是守護(hù)線程,那么程序自動(dòng)停止也就不意外了。下面是來自官方對(duì)虛擬線程與平臺(tái)線程的區(qū)別:

  • 虛擬線程始終是守護(hù)線程。Thread.setDaemon(boolean) 方法無法將虛擬線程更改為非守護(hù)線程。
  • 虛擬線程的固定優(yōu)先級(jí)為 Thread.NORM_PRIORITY。Thread.setPriority(int) 方法對(duì)虛擬線程不起作用。這一限制可能會(huì)在未來的版本中重新考慮。
  • 虛擬線程不是線程組的活動(dòng)成員。在虛擬線程上調(diào)用 Thread.getThreadGroup() 時(shí),會(huì)返回一個(gè)名稱為 "VirtualThreads "的占位線程組。Thread.Builder API 沒有定義設(shè)置虛擬線程線程組的方法。

2.4 KeepAlive虛擬線程

既然虛擬線程是守護(hù)線程,那么要如何解決上面的問題呢?在SpringBoot3.2.0-RC1版本開始為SpringApplication添加"keep-alive"屬性,專門解決虛擬線程問題。

可以通過如下配置開啟keepAlive。

spring:
  main:
    keep-alive: true

通過上面的配置后,再次運(yùn)行上面的程序

圖片圖片

這時(shí)候程序不會(huì)退出了一直運(yùn)行。?

2.5 實(shí)現(xiàn)原理

當(dāng)開啟上面的spring.main.keep-alive=true后,springboot在啟動(dòng)時(shí)會(huì)注冊(cè)一個(gè)監(jiān)聽器。

public class SpringApplication {
  public ConfigurableApplicationContext run(String... args) {
    // ...
    prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);
    // ...
  }
  private void prepareContext(...) {
    // ...
    // SpringBoot在啟動(dòng)時(shí)準(zhǔn)備Environment時(shí)會(huì)自動(dòng)將spring.main下的
    // 屬性配置綁定到當(dāng)前的SpringApplication對(duì)象中(屬性)。
    if (this.keepAlive) {
      // 添加事件監(jiān)聽
      context.addApplicationListener(new KeepAlive());
    }
    // ...
  }
}

事件監(jiān)聽程序KeepAlive。

private static final class KeepAlive implements ApplicationListener<ApplicationContextEvent> {
  public void onApplicationEvent(ApplicationContextEvent event) {
    if (event instanceof ContextRefreshedEvent) {
      // Spring上下文刷新完成
      startKeepAliveThread();
    }
    // Spring容器在關(guān)閉時(shí)
    else if (event instanceof ContextClosedEvent) {
      stopKeepAliveThread();
    }
  }
  private void startKeepAliveThread() {
    // 啟動(dòng)異步線程,一直休眠(保證一直運(yùn)行著,這樣程序就不會(huì)終止了)
    Thread thread = new Thread(() -> {
      while (true) {
        try {
          Thread.sleep(Long.MAX_VALUE);
        }
      }
    });
    if (this.thread.compareAndSet(null, thread)) {
      // 非守護(hù)線程
      thread.setDaemon(false);
      thread.setName("keep-alive");
      thread.start();
    }
  }
  private void stopKeepAliveThread() {
    Thread thread = this.thread.getAndSet(null);
    if (thread == null) {
      return;
    }
    // 終止線程
    thread.interrupt();
  }
}

SpringBoot實(shí)現(xiàn)邏輯還是非常簡單的。

責(zé)任編輯:武曉燕 來源: Spring全家桶實(shí)戰(zhàn)案例源碼
相關(guān)推薦

2024-03-04 08:19:11

SpringURLHeader

2024-01-31 08:26:44

2023-08-07 14:28:07

SpringBoot工具

2022-03-31 07:52:01

Java多線程并發(fā)

2020-02-07 09:27:35

網(wǎng)絡(luò)安全信息安全電子郵件

2023-08-09 08:29:51

SpringWeb編程

2018-04-16 10:22:08

超融合基礎(chǔ)設(shè)施

2022-04-24 09:54:24

ProxyReflect前端

2021-12-03 12:35:50

new[]delete[]語言

2010-11-19 16:02:42

IT族

2020-09-08 14:05:06

Redis數(shù)據(jù)庫緩存

2022-03-21 07:40:08

線程池Executors方式

2022-06-01 16:17:00

互聯(lián)網(wǎng)Web3區(qū)塊鏈

2023-08-11 08:59:49

分庫分表數(shù)據(jù)數(shù)據(jù)庫

2022-10-17 06:22:36

Anaconda開源

2024-10-08 08:26:43

2019-08-21 19:49:21

機(jī)器學(xué)習(xí)人工智能

2021-03-05 11:02:14

iOS 14.5蘋果更新

2015-09-21 09:02:39

java數(shù)組

2024-07-31 14:03:00

Spring定時(shí)任務(wù)管理
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)