欧美亚洲中文,在线国自产视频,欧洲一区在线观看视频,亚洲综合中文字幕在线观看

      1. <dfn id="rfwes"></dfn>
          <object id="rfwes"></object>
        1. 站長(zhǎng)資訊網(wǎng)
          最全最豐富的資訊網(wǎng)站

          Java知識(shí)點(diǎn)總結(jié)之JDK19虛擬線程

          本篇文章給大家?guī)?lái)了關(guān)于java的相關(guān)知識(shí),其中主要介紹了關(guān)于jdk19中虛擬線程的相關(guān)內(nèi)容,虛擬線程是具有和go語(yǔ)言的goroutines 和 Erlang 語(yǔ)言的進(jìn)程類(lèi)似的實(shí)現(xiàn)方式,它們是用戶模式線程的一種形式,下面一起來(lái)看一下,希望對(duì)大家有幫助。

          Java知識(shí)點(diǎn)總結(jié)之JDK19虛擬線程

          程序員必備接口測(cè)試調(diào)試工具:立即使用
          Apipost = Postman + Swagger + Mock + Jmeter
          Api設(shè)計(jì)、調(diào)試、文檔、自動(dòng)化測(cè)試工具
          后端、前端、測(cè)試,同時(shí)在線協(xié)作,內(nèi)容實(shí)時(shí)同步

          推薦學(xué)習(xí):《java視頻教程》

          介紹

          虛擬線程具有和 Go 語(yǔ)言的 goroutines 和 Erlang 語(yǔ)言的進(jìn)程類(lèi)似的實(shí)現(xiàn)方式,它們是用戶模式(user-mode)線程的一種形式。

          在過(guò)去 Java 中常常使用線程池來(lái)進(jìn)行平臺(tái)線程的共享以提高對(duì)計(jì)算機(jī)硬件的使用率,但在這種異步風(fēng)格中,請(qǐng)求的每個(gè)階段可能在不同的線程上執(zhí)行,每個(gè)線程以交錯(cuò)的方式運(yùn)行屬于不同請(qǐng)求的階段,與 Java 平臺(tái)的設(shè)計(jì)不協(xié)調(diào)從而導(dǎo)致:

          • 堆棧跟蹤不提供可用的上下文

          • 調(diào)試器不能單步執(zhí)行請(qǐng)求處理邏輯

          • 分析器不能將操作的成本與其調(diào)用方關(guān)聯(lián)。

          而虛擬線程既保持與平臺(tái)的設(shè)計(jì)兼容,同時(shí)又能最佳地利用硬件從而不影響可伸縮性。虛擬線程是由 JDK 而非操作系統(tǒng)提供的線程的輕量級(jí)實(shí)現(xiàn):

          • 虛擬線程是沒(méi)有綁定到特定操作系統(tǒng)線程的線程。

          • 平臺(tái)線程是以傳統(tǒng)方式實(shí)現(xiàn)的線程,作為圍繞操作系統(tǒng)線程的簡(jiǎn)單包裝。

          摘要

          向 Java 平臺(tái)引入虛擬線程。虛擬線程是輕量級(jí)線程,它可以大大減少編寫(xiě)、維護(hù)和觀察高吞吐量并發(fā)應(yīng)用程序的工作量。

          目標(biāo)

          • 允許以簡(jiǎn)單的每個(gè)請(qǐng)求一個(gè)線程的方式編寫(xiě)的服務(wù)器應(yīng)用程序以接近最佳的硬件利用率進(jìn)行擴(kuò)展。

          • 允許使用 java.lang.ThreadAPI 的現(xiàn)有代碼采用虛擬線程,并且只做最小的更改。

          • 使用現(xiàn)有的 JDK 工具可以方便地對(duì)虛擬線程進(jìn)行故障排除、調(diào)試和分析。

          非目標(biāo)

          • 移除線程的傳統(tǒng)實(shí)現(xiàn)或遷移現(xiàn)有應(yīng)用程序以使用虛擬線程并不是目標(biāo)。

          • 改變 Java 的基本并發(fā)模型。

          • 我們的目標(biāo)不是在 Java 語(yǔ)言或 Java 庫(kù)中提供新的資料平行結(jié)構(gòu)。StreamAPI 仍然是并行處理大型數(shù)據(jù)集的首選方法。

          動(dòng)機(jī)

          近30年來(lái),Java 開(kāi)發(fā)人員一直依賴線程作為并發(fā)服務(wù)器應(yīng)用程序的構(gòu)件。每個(gè)方法中的每個(gè)語(yǔ)句都在一個(gè)線程中執(zhí)行,而且由于 Java 是多線程的,因此執(zhí)行的多個(gè)線程同時(shí)發(fā)生。

          線程是 Java 的并發(fā)單元: 一段順序代碼,與其他這樣的單元并發(fā)運(yùn)行,并且在很大程度上獨(dú)立于這些單元。

          每個(gè)線程都提供一個(gè)堆棧來(lái)存儲(chǔ)本地變量和協(xié)調(diào)方法調(diào)用,以及出錯(cuò)時(shí)的上下文: 異常被同一個(gè)線程中的方法拋出和捕獲,因此開(kāi)發(fā)人員可以使用線程的堆棧跟蹤來(lái)查找發(fā)生了什么。

          線程也是工具的一個(gè)核心概念: 調(diào)試器遍歷線程方法中的語(yǔ)句,分析器可視化多個(gè)線程的行為,以幫助理解它們的性能。

          兩種并發(fā) style

          thread-per-request style

          • 服務(wù)器應(yīng)用程序通常處理彼此獨(dú)立的并發(fā)用戶請(qǐng)求,因此應(yīng)用程序通過(guò)在整個(gè)請(qǐng)求持續(xù)期間為該請(qǐng)求分配一個(gè)線程來(lái)處理請(qǐng)求是有意義的。這種按請(qǐng)求執(zhí)行線程的 style 易于理解、易于編程、易于調(diào)試和配置,因?yàn)樗褂闷脚_(tái)的并發(fā)單元來(lái)表示應(yīng)用程序的并發(fā)單元。

          • 服務(wù)器應(yīng)用程序的可伸縮性受到利特爾定律(Little's Law)的支配,該定律關(guān)系到延遲、并發(fā)性和吞吐量: 對(duì)于給定的請(qǐng)求處理持續(xù)時(shí)間(延遲) ,應(yīng)用程序同時(shí)處理的請(qǐng)求數(shù)(并發(fā)性) 必須與到達(dá)速率(吞吐量) 成正比增長(zhǎng)。

          • 例如,假設(shè)一個(gè)平均延遲為 50ms 的應(yīng)用程序通過(guò)并發(fā)處理 10 個(gè)請(qǐng)求實(shí)現(xiàn)每秒 200 個(gè)請(qǐng)求的吞吐量。為了使該應(yīng)用程序的吞吐量達(dá)到每秒 2000 個(gè)請(qǐng)求,它將需要同時(shí)處理 100 個(gè)請(qǐng)求。如果在請(qǐng)求持續(xù)期間每個(gè)請(qǐng)求都在一個(gè)線程中處理,那么為了讓?xiě)?yīng)用程序跟上,線程的數(shù)量必須隨著吞吐量的增長(zhǎng)而增長(zhǎng)。

          • 不幸的是,可用線程的數(shù)量是有限的,因?yàn)?JDK 將線程實(shí)現(xiàn)為操作系統(tǒng)(OS)線程的包裝器。操作系統(tǒng)線程代價(jià)高昂,因此我們不能擁有太多線程,這使得實(shí)現(xiàn)不適合每個(gè)請(qǐng)求一個(gè)線程的 style 。

          • 如果每個(gè)請(qǐng)求在其持續(xù)時(shí)間內(nèi)消耗一個(gè)線程,從而消耗一個(gè) OS 線程,那么線程的數(shù)量通常會(huì)在其他資源(如 CPU 或網(wǎng)絡(luò)連接)耗盡之前很久成為限制因素。JDK 當(dāng)前的線程實(shí)現(xiàn)將應(yīng)用程序的吞吐量限制在遠(yuǎn)低于硬件所能支持的水平。即使在線程池中也會(huì)發(fā)生這種情況,因?yàn)槌赜兄诒苊鈫?dòng)新線程的高成本,但不會(huì)增加線程的總數(shù)。

          asynchronous style

          一些希望充分利用硬件的開(kāi)發(fā)人員已經(jīng)放棄了每個(gè)請(qǐng)求一個(gè)線程(thread-per-request) 的 style ,轉(zhuǎn)而采用線程共享(thread-sharing ) 的 style 。

          請(qǐng)求處理代碼不是從頭到尾處理一個(gè)線程上的請(qǐng)求,而是在等待 I/O 操作完成時(shí)將其線程返回到一個(gè)池中,以便該線程能夠處理其他請(qǐng)求。這種細(xì)粒度的線程共享(其中代碼只在執(zhí)行計(jì)算時(shí)保留一個(gè)線程,而不是在等待 I/O 時(shí)保留該線程)允許大量并發(fā)操作,而不需要消耗大量線程。

          雖然它消除了操作系統(tǒng)線程的稀缺性對(duì)吞吐量的限制,但代價(jià)很高: 它需要一種所謂的異步編程 style ,采用一組獨(dú)立的 I/O 方法,這些方法不等待 I/O 操作完成,而是在以后將其完成信號(hào)發(fā)送給回調(diào)。如果沒(méi)有專門(mén)的線程,開(kāi)發(fā)人員必須將請(qǐng)求處理邏輯分解成小的階段,通常以 lambda 表達(dá)式的形式編寫(xiě),然后將它們組合成帶有 API 的順序管道(例如,參見(jiàn) CompletableFuture,或者所謂的“反應(yīng)性”框架)。因此,它們放棄了語(yǔ)言的基本順序組合運(yùn)算符,如循環(huán)和 try/catch 塊。

          在異步樣式中,請(qǐng)求的每個(gè)階段可能在不同的線程上執(zhí)行,每個(gè)線程以交錯(cuò)的方式運(yùn)行屬于不同請(qǐng)求的階段。這對(duì)于理解程序行為有著深刻的含義:

          • 堆棧跟蹤不提供可用的上下文

          • 調(diào)試器不能單步執(zhí)行請(qǐng)求處理邏輯

          • 分析器不能將操作的成本與其調(diào)用方關(guān)聯(lián)。

          當(dāng)使用 Java 的流 API 在短管道中處理數(shù)據(jù)時(shí),組合 lambda 表達(dá)式是可管理的,但是當(dāng)應(yīng)用程序中的所有請(qǐng)求處理代碼都必須以這種方式編寫(xiě)時(shí),就有問(wèn)題了。這種編程 style 與 Java 平臺(tái)不一致,因?yàn)閼?yīng)用程序的并發(fā)單元(異步管道)不再是平臺(tái)的并發(fā)單元。

          對(duì)比

          Java知識(shí)點(diǎn)總結(jié)之JDK19虛擬線程

          使用虛擬線程保留thread-per-request style

          為了使應(yīng)用程序能夠在與平臺(tái)保持和諧的同時(shí)進(jìn)行擴(kuò)展,我們應(yīng)該通過(guò)更有效地實(shí)現(xiàn)線程來(lái)努力保持每個(gè)請(qǐng)求一個(gè)線程的 style ,以便它們能夠更加豐富。

          操作系統(tǒng)無(wú)法更有效地實(shí)現(xiàn) OS 線程,因?yàn)椴煌恼Z(yǔ)言和運(yùn)行時(shí)以不同的方式使用線程堆棧。然而,Java 運(yùn)行時(shí)實(shí)現(xiàn) Java 線程的方式可以切斷它們與操作系統(tǒng)線程之間的一一對(duì)應(yīng)關(guān)系。正如操作系統(tǒng)通過(guò)將大量虛擬地址空間映射到有限數(shù)量的物理 RAM 而給人一種內(nèi)存充足的錯(cuò)覺(jué)一樣,Java 運(yùn)行時(shí)也可以通過(guò)將大量虛擬線程映射到少量操作系統(tǒng)線程而給人一種線程充足的錯(cuò)覺(jué)。

          • 虛擬線程是沒(méi)有綁定到特定操作系統(tǒng)線程的線程。

          • 平臺(tái)線程是以傳統(tǒng)方式實(shí)現(xiàn)的線程,作為圍繞操作系統(tǒng)線程的簡(jiǎn)單包裝。

          thread-per-request 樣式的應(yīng)用程序代碼可以在整個(gè)請(qǐng)求期間在虛擬線程中運(yùn)行,但是虛擬線程只在 CPU 上執(zhí)行計(jì)算時(shí)使用操作系統(tǒng)線程。其結(jié)果是與異步樣式相同的可伸縮性,除了它是透明實(shí)現(xiàn)的:

          當(dāng)在虛擬線程中運(yùn)行的代碼調(diào)用 Java.* API 中的阻塞 I/O 操作時(shí),運(yùn)行時(shí)執(zhí)行一個(gè)非阻塞操作系統(tǒng)調(diào)用,并自動(dòng)掛起虛擬線程,直到稍后可以恢復(fù)。

          對(duì)于 Java 開(kāi)發(fā)人員來(lái)說(shuō),虛擬線程是創(chuàng)建成本低廉、數(shù)量幾乎無(wú)限多的線程。硬件利用率接近最佳,允許高水平的并發(fā)性,從而提高吞吐量,而應(yīng)用程序仍然與 Java 平臺(tái)及其工具的多線程設(shè)計(jì)保持協(xié)調(diào)。

          虛擬線程的意義

          虛擬線程是廉價(jià)和豐富的,因此永遠(yuǎn)不應(yīng)該被共享(即使用線程池) : 應(yīng)該為每個(gè)應(yīng)用程序任務(wù)創(chuàng)建一個(gè)新的虛擬線程。

          因此,大多數(shù)虛擬線程的壽命都很短,并且具有淺層調(diào)用堆棧,執(zhí)行的操作只有單個(gè) HTTP 客戶機(jī)調(diào)用或單個(gè) JDBC 查詢那么少。相比之下,平臺(tái)線程是重量級(jí)和昂貴的,因此經(jīng)常必須共享。它們往往是長(zhǎng)期存在的,具有深度調(diào)用堆棧,并且在許多任務(wù)之間共享。

          總之,虛擬線程保留了可靠的 thread-per-request style ,這種 style 與 Java 平臺(tái)的設(shè)計(jì)相協(xié)調(diào),同時(shí)又能最佳地利用硬件。使用虛擬線程并不需要學(xué)習(xí)新的概念,盡管它可能需要為應(yīng)對(duì)當(dāng)今線程的高成本而養(yǎng)成的忘卻習(xí)慣。虛擬線程不僅可以幫助應(yīng)用程序開(kāi)發(fā)人員ーー它們還可以幫助框架設(shè)計(jì)人員提供易于使用的 API,這些 API 與平臺(tái)的設(shè)計(jì)兼容,同時(shí)又不影響可伸縮性。

          說(shuō)明

          如今,java.lang 的每一個(gè)實(shí)例。JDK 中的線程是一個(gè)平臺(tái)線程。平臺(tái)線程在底層操作系統(tǒng)線程上運(yùn)行 Java 代碼,并在代碼的整個(gè)生命周期中捕獲操作系統(tǒng)線程。平臺(tái)線程的數(shù)量?jī)H限于操作系統(tǒng)線程的數(shù)量。

          虛擬線程是 java.lang 的一個(gè)實(shí)例。在基礎(chǔ)操作系統(tǒng)線程上運(yùn)行 Java 代碼,但在代碼的整個(gè)生命周期中不捕獲該操作系統(tǒng)線程的線程。這意味著許多虛擬線程可以在同一個(gè) OS 線程上運(yùn)行它們的 Java 代碼,從而有效地共享它們。平臺(tái)線程壟斷了一個(gè)珍貴的操作系統(tǒng)線程,而虛擬線程卻沒(méi)有。虛擬線程的數(shù)量可能比操作系統(tǒng)線程的數(shù)量大得多。

          虛擬線程是由 JDK 而非操作系統(tǒng)提供的線程的輕量級(jí)實(shí)現(xiàn)。它們是用戶模式(user-mode)線程的一種形式,已經(jīng)在其他多線程語(yǔ)言中取得了成功(例如,Go 中的 goroutines 和 Erlang 的進(jìn)程)。在 Java 的早期版本中,用戶模式線程甚至以所謂的“綠線程”為特色,當(dāng)時(shí) OS 線程還不成熟和普及。然而,Java 的綠色線程都共享一個(gè) OS 線程(M: 1調(diào)度) ,并最終被平臺(tái)線程超越,實(shí)現(xiàn)為 OS 線程的包裝器(1:1調(diào)度)。虛擬線程采用 M: N 調(diào)度,其中大量(M)虛擬線程被調(diào)度在較少(N)操作系統(tǒng)線程上運(yùn)行。

          虛擬線程 VS 平臺(tái)線程

          簡(jiǎn)單示例

          開(kāi)發(fā)人員可以選擇使用虛擬線程還是平臺(tái)線程。下面是一個(gè)創(chuàng)建大量虛擬線程的示例程序。該程序首先獲得一個(gè) ExecutorService,它將為每個(gè)提交的任務(wù)創(chuàng)建一個(gè)新的虛擬線程。然后,它提交10000項(xiàng)任務(wù),等待所有任務(wù)完成:

          try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {     IntStream.range(0, 10000).forEach(i -> {         executor.submit(() -> {             Thread.sleep(Duration.ofSeconds(1));             return i;         });     }); }  // executor.close() is called implicitly, and waits
          登錄后復(fù)制

          本例中的任務(wù)是簡(jiǎn)單的代碼(休眠一秒鐘) ,現(xiàn)代硬件可以輕松支持10,000個(gè)虛擬線程并發(fā)運(yùn)行這些代碼。在幕后,JDK 在少數(shù)操作系統(tǒng)線程上運(yùn)行代碼,可能只有一個(gè)線程。

          如果這個(gè)程序使用 ExecutorService 為每個(gè)任務(wù)創(chuàng)建一個(gè)新的平臺(tái)線程,比如 Executors.newCachedThreadPool () ,那么情況就會(huì)大不相同。ExecutorService 將嘗試創(chuàng)建10,000個(gè)平臺(tái)線程,從而創(chuàng)建10,000個(gè) OS 線程,程序可能會(huì)崩潰,這取決于計(jì)算機(jī)和操作系統(tǒng)。

          相反,如果程序使用從池中獲取平臺(tái)線程的 ExecutorService (例如 Executors.newFixedThreadPool (200)) ,情況也不會(huì)好到哪里去。ExecutorService 將創(chuàng)建200個(gè)平臺(tái)線程,由所有10,000個(gè)任務(wù)共享,因此許多任務(wù)將按順序運(yùn)行,而不是并發(fā)運(yùn)行,而且程序?qū)⑿枰荛L(zhǎng)時(shí)間才能完成。對(duì)于這個(gè)程序,一個(gè)有200個(gè)平臺(tái)線程的池只能達(dá)到每秒200個(gè)任務(wù)的吞吐量,而虛擬線程達(dá)到每秒10,000個(gè)任務(wù)的吞吐量(在充分預(yù)熱之后)。此外,如果示例程序中的10000被更改為1000000,那么該程序?qū)⑻峤?,000,000個(gè)任務(wù),創(chuàng)建1,000,000個(gè)并發(fā)運(yùn)行的虛擬線程,并且(在足夠的預(yù)熱之后)實(shí)現(xiàn)大約1,000,000任務(wù)/秒的吞吐量。

          如果這個(gè)程序中的任務(wù)執(zhí)行一秒鐘的計(jì)算(例如,對(duì)一個(gè)巨大的數(shù)組進(jìn)行排序)而不僅僅是休眠,那么增加超出處理器核心數(shù)量的線程數(shù)量將無(wú)濟(jì)于事,無(wú)論它們是虛擬線程還是平臺(tái)線程。

          虛擬線程并不是更快的線程ーー它們運(yùn)行代碼的速度并不比平臺(tái)線程快。它們的存在是為了提供規(guī)模(更高的吞吐量) ,而不是速度(更低的延遲) 。它們的數(shù)量可能比平臺(tái)線程多得多,因此根據(jù) Little’s Law,它們能夠?qū)崿F(xiàn)更高吞吐量所需的更高并發(fā)性。

          換句話說(shuō),虛擬線程可以顯著提高應(yīng)用程序的吞吐量,在如下情況時(shí):

          • 并發(fā)任務(wù)的數(shù)量很多(超過(guò)幾千個(gè))

          • 工作負(fù)載不受 CPU 限制,因?yàn)樵谶@種情況下,比處理器核心擁有

          贊(0)
          分享到: 更多 (0)
          網(wǎng)站地圖   滬ICP備18035694號(hào)-2    滬公網(wǎng)安備31011702889846號(hào)