深入理解高并發編程 - 深度解析 ThreadPoolExecutor 類

ThreadPoolExecutor 是 Java 標準庫中用于創建和管理線程池的核心類之一。它實現了 ExecutorService 接口,提供了豐富的線程池管理功能。下面將通過源碼解析來深入了解 ThreadPoolExecutor 類的工作原理和各個重要部分。

可以在 Java 源代碼中找到 ThreadPoolExecutor 類的實現,位于 java.util.concurrent 包中。

以下是 ThreadPoolExecutor 類的一些關鍵概念和部分:

1、構造函數:

ThreadPoolExecutor 類提供了幾個不同的構造函數,允許傳遞核心線程數、最大線程數、空閑線程存活時間、任務隊列等參數。這些參數決定了線程池的基本行為。
下面是幾個常用的構造函數及其參數的解釋:

核心線程數 (corePoolSize):
核心線程數表示線程池中始終保持存活的線程數量。這些線程會一直存在,即使它們處于空閑狀態。線程池會根據任務數量自動創建新線程,直到核心線程數達到上限。最大線程數 (maximumPoolSize):
最大線程數表示線程池中最多可以同時存在的線程數量。當核心線程數已滿,且任務隊列也已滿時,線程池會創建新線程,直到最大線程數達到上限。如果達到最大線程數后仍有更多任務到達,根據飽和策略進行處理。空閑線程存活時間 (keepAliveTime):
空閑線程存活時間表示當線程池中的線程數超過核心線程數,并且這些線程處于空閑狀態時,它們會被保留的時間。超過此時間后,多余的空閑線程將被終止,從而節省系統資源。時間單位 (unit):
時間單位用于指定空閑線程存活時間的單位,可以是秒、毫秒等。任務隊列 (workQueue):
任務隊列用于存儲等待執行的任務。線程池可以使用不同類型的隊列,如 BlockingQueue 的各種實現。任務隊列決定了等待執行的任務數量。線程工廠 (threadFactory):
線程工廠用于創建新線程,默認使用 Executors.defaultThreadFactory()。可以自定義線程工廠來創建線程,從而指定線程的名稱、優先級等屬性。飽和策略 (handler):
當線程池和任務隊列都已滿,新的任務到達時,飽和策略定義了如何處理這些任務。線程池提供了幾種內置的飽和策略,如 AbortPolicy、CallerRunsPolicy、DiscardPolicy 以及 DiscardOldestPolicy。

2、線程池狀態:

ThreadPoolExecutor 的內部維護了幾種狀態,包括 RUNNING、SHUTDOWN、STOP、TIDYING 和 TERMINATED。線程池在不同的狀態下會有不同的行為,例如當調用 shutdown 方法時,線程池會從 RUNNING 狀態轉變為 SHUTDOWN 狀態,不再接受新的任務。

RUNNING(運行中):
在 RUNNING 狀態下,線程池處于正常運行狀態,可以接受新任務并執行已提交的任務。在這個狀態下,核心線程數和非核心線程數都可以創建和執行任務。SHUTDOWN(關閉中):
當調用線程池的 shutdown 方法時,線程池會進入 SHUTDOWN 狀態。在這個狀態下,線程池不再接受新任務,但會繼續執行已提交的任務,包括等待隊列中的任務。STOP(立即停止):
當調用線程池的 shutdownNow 方法時,線程池會進入 STOP 狀態。在這個狀態下,線程池會嘗試中斷所有正在執行的線程,并清空任務隊列。TIDYING(整理中):
當線程池狀態從 SHUTDOWN 轉變為 TIDYING,表示線程池已經停止接受新任務,正在執行中的任務也已經完成,處于整理和清理狀態。在這個狀態下,線程池會執行一些清理操作,例如中斷空閑線程。TERMINATED(已終止):
當線程池狀態從 TIDYING 轉變為 TERMINATED,表示線程池已經徹底終止,所有任務都已執行完畢,并且線程池中的所有線程都已銷毀。在這個狀態下,線程池不再執行任何操作。

3、任務隊列:

任務隊列用于存儲等待執行的任務。ThreadPoolExecutor 允許使用不同類型的隊列,如 BlockingQueue 的各種實現,包括 LinkedBlockingQueue、ArrayBlockingQueue 等。這些隊列控制了等待執行的任務數量。
以下是幾種常見的任務隊列類型及其特點:

LinkedBlockingQueue(鏈式阻塞隊列):
LinkedBlockingQueue 是一個基于鏈表的無界阻塞隊列,它可以存儲無限數量的任務。在核心線程數未滿的情況下,新的任務會直接創建新線程來執行。當核心線程數已滿時,任務會被放入隊列中等待執行。ArrayBlockingQueue(數組阻塞隊列):
ArrayBlockingQueue 是一個基于數組的有界阻塞隊列,需要指定隊列的容量。在核心線程數未滿的情況下,新的任務會直接創建新線程來執行。當核心線程數已滿時,任務會被放入隊列中等待執行。PriorityBlockingQueue(優先級阻塞隊列):
PriorityBlockingQueue 是一個無界阻塞隊列,它會根據任務的優先級來進行調度。具有較高優先級的任務會被優先執行。DelayedWorkQueue(延遲工作隊列):
DelayedWorkQueue 是一個用于調度延遲任務的隊列,其中的任務可以設置延遲執行時間。適用于需要按照一定延遲執行任務的場景。SynchronousQueue(同步隊列):
SynchronousQueue 是一個沒有實際存儲能力的隊列,每個插入操作必須等待一個相應的刪除操作,反之亦然。適用于需要實現一對一的任務交付機制。

用了無界隊列那非核心線程就不會創建了?

是的,當線程池使用無界隊列(如 LinkedBlockingQueue 或 PriorityBlockingQueue)時,非核心線程的創建將受到影響。在無界隊列中,任務可以無限制地排隊等待執行,因此不會觸發創建額外的非核心線程。

對于無界隊列,以下是一些關鍵點需要注意:

核心線程數 (corePoolSize): 核心線程數仍然會影響線程池的初始線程創建,當任務提交到線程池時,如果當前活動線程數小于核心線程數,新任務會創建一個核心線程來執行。這適用于線程池的初始階段或在任務數量較少的情況下。最大線程數 (maximumPoolSize): 如果任務的到達速率超過了核心線程的執行速度,并且任務隊列已滿,非核心線程將不會創建,因為無界隊列可以容納所有任務。任務隊列: 無界隊列會持續地存儲等待執行的任務,直到系統資源用盡。這可能導致內存占用逐漸增加,因此在選擇無界隊列時需要注意系統資源的管理。

無界隊列適用于一些特定場景,如任務量波動大,任務執行時間差異較大,或者希望盡量保留任務而不丟失的情況。然而,需要注意的是,如果任務數量持續增加,無界隊列可能會導致內存消耗過大,因此在選擇隊列類型時需要綜合考慮線程池的整體性能和資源利用。

阻塞隊列?

前面提到的 LinkedBlockingQueue、PriorityBlockingQueue 和 ArrayBlockingQueue 都是阻塞隊列,這里我將更詳細地解釋一下阻塞隊列的概念以及它們的作用。

阻塞隊列是一種特殊類型的隊列,具有以下特點:

阻塞特性: 當向隊列添加元素或從隊列中取出元素時,如果隊列已滿或為空,阻塞隊列會自動阻塞線程,直到隊列變為非滿或非空為止。線程安全: 阻塞隊列是線程安全的,多個線程可以并發地進行入隊和出隊操作,而不需要額外的同步措施。

在 ThreadPoolExecutor 中,任務隊列是一個關鍵組件,它決定了線程池中等待執行的任務數量、調度策略以及如何處理任務。不同類型的阻塞隊列在不同的情況下有不同的用途和特點,可以根據實際需求進行選擇。

具體來說:

LinkedBlockingQueue 是一個基于鏈表的無界阻塞隊列。當任務數量超過核心線程數時,新的任務會被放入隊列中等待執行。如果隊列已滿,新任務會阻塞等待直到有空間。
ArrayBlockingQueue 是一個基于數組的有界阻塞隊列。它需要指定隊列的容量。當隊列已滿時,新的任務會阻塞等待直到有空間。
PriorityBlockingQueue 是一個無界阻塞隊列,根據元素的優先級來進行調度。優先級高的元素會被先出隊執行。

請注意,阻塞隊列適用于不同的場景和需求。根據應用特性和性能要求,選擇適合的阻塞隊列類型是很重要的。

LinkedBlockingQueue無界隊列?

LinkedBlockingQueue 是一個基于鏈表的可選界限阻塞隊列,而不是無界隊列。這意味著它可以選擇性地指定隊列的容量,當容量未指定時,隊列會默認為無界。

所以,當使用 LinkedBlockingQueue 作為任務隊列時,如果沒有指定容量(或者容量為 Integer.MAX_VALUE),隊列會被認為是無界的,新任務總是可以放入隊列中,而不會因為隊列已滿而阻塞。

如果指定了容量,當任務數量超過容量時,新的任務會被放入隊列中等待執行。當隊列已滿時,新任務會阻塞等待直到有空間,這是典型的阻塞隊列行為。

4、線程工廠:

ThreadPoolExecutor 允許通過提供線程工廠來自定義線程的創建過程,包括線程的名稱、優先級、是否守護線程等屬性。線程工廠負責創建新的線程實例,然后線程池會使用這些線程來執行任務。

ThreadPoolExecutor 的構造函數中有一個參數 threadFactory,可以傳遞一個實現了 ThreadFactory 接口的對象來指定線程工廠。ThreadFactory 接口只有一個方法 newThread,用于創建新的線程。以下是一個簡單的示例:

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;public class CustomThreadFactoryExample {public static void main(String[] args) {ThreadFactory threadFactory = new CustomThreadFactory("MyThreadGroup");ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 1, TimeUnit.SECONDS,new LinkedBlockingQueue<>(), threadFactory);// ... 添加任務到線程池并執行 ...executor.shutdown();}
}class CustomThreadFactory implements ThreadFactory {private final ThreadGroup threadGroup;private final AtomicInteger threadNumber = new AtomicInteger(1);public CustomThreadFactory(String threadGroupName) {SecurityManager s = System.getSecurityManager();threadGroup = (s != null) ? s.getThreadGroup() :Thread.currentThread().getThreadGroup();}public Thread newThread(Runnable r) {Thread thread = new Thread(threadGroup, r,"MyThread-" + threadNumber.getAndIncrement(),0);if (thread.isDaemon())thread.setDaemon(false);if (thread.getPriority() != Thread.NORM_PRIORITY)thread.setPriority(Thread.NORM_PRIORITY);return thread;}
}

在上面的示例中,創建了一個自定義的線程工廠 CustomThreadFactory 實現了 ThreadFactory 接口,用于創建具有自定義屬性的線程。在這個例子中,指定了線程組、線程名稱前綴等屬性。

通過使用線程工廠,可以為線程池中的每個線程設置特定的屬性,從而更好地管理和控制線程的行為。這對于在多線程應用程序中調試和監視線程非常有幫助。

5、飽和策略:

當線程池和任務隊列都已滿,新的任務到達時,飽和策略定義了如何處理這些任務。ThreadPoolExecutor 提供了幾種內置的飽和策略,如 AbortPolicy(拋出異常)、CallerRunsPolicy(由調用者線程執行任務)、DiscardPolicy(丟棄任務)以及 DiscardOldestPolicy(丟棄最舊的任務)。
下面對每種策略進行詳細解釋:

AbortPolicy(拋出異常):
這是默認的飽和策略。當線程池和隊列都已滿時,新任務會導致 RejectedExecutionException 異常被拋出,提示線程池已經飽和。這是一種保守的策略,防止任務丟失。CallerRunsPolicy(由調用者線程執行任務):
當線程池和隊列都已滿時,新任務會被調用者線程(提交任務的線程)直接執行,而不會交給線程池中的線程來執行。這種策略可能會導致調用者線程阻塞,因為它們會等待任務執行完畢。DiscardPolicy(丟棄任務):
當線程池和隊列都已滿時,新任務會被直接丟棄,不會進行任何處理。這可能會導致任務丟失,慎用此策略。DiscardOldestPolicy(丟棄最舊的任務):
當線程池和隊列都已滿時,會嘗試將最早的任務從隊列中移除,然后添加新任務。這可能會導致一些舊任務被丟棄,以便為新任務騰出空間。

這些飽和策略提供了不同的處理方式,可以根據應用需求來選擇合適的策略。通常情況下,AbortPolicy 是默認的且安全的選擇,因為它會在資源不足時拋出異常,提示應該考慮調整線程池大小或處理任務隊列。其他策略可能會導致任務丟失或阻塞,需要根據具體情況謹慎選擇。

6、線程池的執行過程:

當任務提交給 ThreadPoolExecutor 后,線程池會根據當前狀態、核心線程數、任務隊列狀態等決定任務的處理方式。如果核心線程數未滿,會創建新線程來執行任務;如果核心線程數已滿,會嘗試將任務放入隊列,如果隊列也已滿,會根據飽和策略來處理任務。

描述的流程如下:

核心線程數未滿:
如果線程池的當前活動線程數小于核心線程數,線程池會創建一個新的核心線程來立即執行提交的任務。核心線程數已滿:
如果線程池的當前活動線程數達到核心線程數,新任務會被放入任務隊列等待執行。隊列未滿:
如果任務隊列未滿,新任務會被放入隊列中等待執行。隊列已滿:
如果任務隊列已滿,根據選擇的飽和策略來處理任務。可能的策略包括:拋出異常(AbortPolicy):如果飽和策略為 AbortPolicy,則新任務會被拒絕,并拋出 RejectedExecutionException 異常。由調用者線程執行(CallerRunsPolicy):如果飽和策略為 CallerRunsPolicy,則提交任務的線程(調用者線程)會執行該任務,而不會交給線程池中的線程執行。丟棄任務(DiscardPolicy):如果飽和策略為 DiscardPolicy,則新任務會被直接丟棄,不會進行任何處理。丟棄最舊的任務(DiscardOldestPolicy):如果飽和策略為 DiscardOldestPolicy,則嘗試從隊列中移除最舊的任務,以便為新任務騰出空間。

線程池根據這些步驟來動態調整線程的創建和任務的處理,以適應不同的并發情況和資源限制。這種靈活的處理方式使得線程池能夠在不同的負載下保持高效的任務處理能力。

7、線程池的終止:

調用 shutdown 方法會觸發線程池的終止過程。線程池會拒絕新的任務,等待已提交但未執行的任務完成,然后關閉線程池中的線程。

調用 shutdown 方法是線程池的一種優雅關閉方式。下面將更詳細地解釋 shutdown 方法的作用和線程池的終止過程:

調用 shutdown 方法:
當調用線程池的 shutdown 方法時,線程池會開始終止的過程。在此過程中,線程池將不再接受新的任務,但會繼續執行已提交但尚未執行的任務,同時等待隊列中的任務也會被繼續執行。任務執行和隊列處理:
在終止過程中,線程池會讓已經創建的核心線程和非核心線程繼續處理已提交的任務。同時,線程池也會嘗試從任務隊列中獲取任務來執行。如果隊列中還有等待執行的任務,線程池會繼續分配線程來執行這些任務。拒絕新任務:
在調用 shutdown 方法后,線程池會拒絕接受新的任務。任何嘗試提交新任務的操作都會被拒絕,并且會拋出 RejectedExecutionException 異常。等待任務完成:
在終止過程中,線程池會等待隊列中的任務和正在執行的任務都完成。這意味著線程池不會立即關閉,而是會等待任務全部執行完畢。關閉線程池中的線程:
一旦所有任務都執行完畢,線程池會關閉其中的線程。如果線程池中存在非核心線程,它們在任務執行完畢后會根據 keepAliveTime 和空閑時間來判斷是否終止。終止狀態:
當線程池中的所有線程都已關閉時,線程池會達到 TERMINATED 狀態,表示線程池已經完全終止。

總之,調用 shutdown 方法后,線程池會等待已提交但未執行的任務完成,并且關閉線程池中的線程,最終達到終止狀態。這種方式可以避免任務丟失,并且允許線程池逐步優雅地停止,釋放資源。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/37180.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/37180.shtml
英文地址,請注明出處:http://en.pswp.cn/news/37180.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【新品發布】ChatWork企業知識庫系統源碼

系統簡介 基于前后端分離架構以及Vue3、uni-app、ThinkPHP6.x、PostgreSQL、pgvector技術棧開發&#xff0c;包含PC端、H5端。 ChatWork支持問答式和文檔式知識庫&#xff0c;能夠導入txt、doc、docx、pdf、md等多種格式文檔。 導入數據完成向量化訓練后&#xff0c;用戶提問…

兩個pdf合并成一個pdf怎么合并?這幾個方法值得推薦

兩個pdf合并成一個pdf怎么合并&#xff1f;pdf文件的合并是一個很常見的需求&#xff0c;特別是在處理工作文件或學習資料時。為了更好的幫助你了解如何將兩個pdf文件合并成一個&#xff0c;下面就給大家詳細介紹幾種合并方法。 方法一&#xff1a;使用迅捷PDF轉換器 這是一款…

小紅書如何打造爆款引流吸粉?11個秘訣助你秒變達人!

在這個充滿信息和內容的時代&#xff0c;小紅書以其獨特的社交平臺特性和個性化內容吸引了眾多用戶。今天&#xff0c;我們就來揭秘小紅書關注戰略&#xff0c;了解如何在這個平臺上打造獨特的內容體驗&#xff0c;與用戶建立更親近的連接。#小紅書# 1、定位清晰&#xff0c;找…

【論文閱讀】基于深度學習的時序預測——Pyraformer

系列文章鏈接 論文一&#xff1a;2020 Informer&#xff1a;長時序數據預測 論文二&#xff1a;2021 Autoformer&#xff1a;長序列數據預測 論文三&#xff1a;2022 FEDformer&#xff1a;長序列數據預測 論文四&#xff1a;2022 Non-Stationary Transformers&#xff1a;非平…

Python技巧----解壓序列/可迭代對象賦值給多個變量

1 、解壓序列賦值給多個變量 我們這里說的不是正常情況的一一賦值比如下面 >>> data = [ ACME, 5, 9, (2012, 12, 1) ] >>> name, shares, price, date = data >>> name ACME

頁面跳轉和兩個頁面之間的數據傳遞-鴻蒙ArkTS

頁面跳轉和兩個頁面之間的數據傳遞-ArkTS 頁面跳轉和兩個頁面之間的數據傳遞-ArkTS關于router的使用**跳轉頁面的實現方式。**頁面接受跳轉傳遞的參數頁面返回及攜帶參數效果代碼Index頁面Second頁面 參考資料 頁面跳轉和兩個頁面之間的數據傳遞-ArkTS 本篇文章主要是對兩個頁面…

TiDB在科捷物流神州金庫核心系統的應用與實踐

業務背景 北京科捷物流有限公司于2003年在北京正式成立&#xff0c;是ISO質量管理體系認證企業、國家AAAAA級物流企業、海關AEO高級認證企業&#xff0c;注冊資金1億元&#xff0c;是中國領先的大數據科技公司——神州控股的全資子公司。科捷物流融合B2B和B2C的客戶需求&#…

網易有道押寶大模型,打響智能硬件突圍戰

本文轉載自產業科技 自今年開年以來&#xff0c;AI大模型這場火勢能不減&#xff0c;如今已燃到教育領域。 7月26日&#xff0c;網易有道舉辦了“powered by子曰”教育大模型應用成果發布會&#xff0c;推出國內首個教育領域垂直大模型“子曰”&#xff0c;并一口氣發布了基于…

conda - 調研介紹

介紹: conda 是一個工具, 也是一個可執行命令, 其核心功能是管理包與環境. conda 支持多種語言, 用來管理Python包是綽綽有余的. 這里注意區分conda和pip, pip命令可以在任何環境中安裝Python包, 而conda則是在conda環境中安裝任何語言包. 接觸過的conda主要有miniconda與anac…

matlab使用教程(15)—圖論基礎

1.有向圖和無向圖 1.1什么是圖&#xff1f; 圖是表示各種關系的節點和邊的集合&#xff1a; ? 節點 是與對象對應的頂點。 ? 邊 是對象之間的連接。 ? 圖的邊有時會有權重 &#xff0c;表示節點之間的每個連接的強度&#xff08;或一些其他屬性&#xff09;。 這些定…

MySQL8.xx一主兩從復制安裝與配置

搭建環境: 查看系統版本cat /etc/redhat-release [rootwww tools]# cat /etc/redhat-release CentOS Linux release 7.9.2009 (Core) 查看內核版本cat /proc/version 目標: 一主兩從 主機IP 主機名稱 端口 搭建環境 安裝目錄192.168.1.100 docker…

MobaXterm sftp 不能拖拽文件夾了?

原因是我把mobaxterm設置成Windows管理員權限運行了,結果就不能拖動文件。把管理員權限去掉就恢復正常了。 原因是我把mobaxterm設置成Windows管理員權限運行了,結果就不能拖動文件。把管理員權限去掉就恢復正常了。 原因是我把mobaxterm設置成Windows管理員權限運行了,結果就不…

19.正則表達式

19.1什么是正則表達式 ●正則表達式( Regular Expression) 是用于匹配字符串中字符組合的模式。在JavaScript中&#xff0c; 正則表達式也是對象 ●通常用來查找、替換那些符合正則表達式的文本&#xff0c;許多語言都支持正則表達式 ●正則表達式在JavaScript中的使用場景: …

什么是MCU芯片?分類有哪些?與MPU、SoC的區別

1. MCU芯片 MCU&#xff0c;全稱為微控制單元&#xff0c;可以看作是CPU頻率和規格的縮減。它整合了計數器、內存、USB和A/D轉換等功能&#xff0c;形成了一個芯片級的計算機。MCU的重要性僅次于CPU&#xff0c;廣泛應用于各種應用場景&#xff0c;如校園卡、身份證、家用電器…

《golang設計模式》第二部分·結構型模式-02-橋接模式(Bridge)

文章目錄 1. 概念1.1 角色1.2 類圖 2. 代碼示例2.1 設計2.1 代碼2.2 類圖 1. 概念 客戶端調用橋接接口實現原有功能和擴展功能的組合 1.1 角色 Implementor&#xff08;實施者&#xff09;&#xff1a; 具體實施者的抽象&#xff0c;可以是一個接口。 Concrete Implementor&…

8.15號經典模型復習筆記

文章目錄 Deep Residual Learning for Image Recognition(CVPR2016)方法 Densely Connected Convolutional Networks&#xff08;CVPR2017&#xff09;方法 EfficientNet: Rethinking Model Scaling for Convolutional Neural Networks&#xff08;ICML2019&#xff09;方法 Re…

使用維納過濾器消除駕駛艙噪音(Matlab代碼實現)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;歡迎來到本博客????&#x1f4a5;&#x1f4a5; &#x1f3c6;博主優勢&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客內容盡量做到思維縝密&#xff0c;邏輯清晰&#xff0c;為了方便讀者。 ??座右銘&a…

信息論、推理和機器學習算法之間交叉的經典例子

信息論、推理和機器學習算法之間交叉的經典例子: 熵和信息增益在決策樹學習中的應用。信息增益利用熵的概念來評估特征的分類能力,從而指導決策樹的增長。 交叉熵在神經網絡訓練中的廣泛使用。它結合信息論與最大似然推斷,用于度量預測分布與真實分布之間的距離。 變分推斷常被…

Tomcat的多實例和動靜分離

目錄 一、多實例 二、 nginxtomcat的負載均衡和動靜分離 三、Tomcat 客戶端->四層代理->七層代理->tomcat服務器 實驗&#xff1a; 問題總結&#xff1a; tomcat日志文件&#xff1a;/usr/local/tomcat/logs/catalina.out 一、多實例 在一臺服務器上有多個tomc…

微信小程序(原生)和uniapp預覽電子文件doc/pdf/ppt/excel等

微信小程序原生預覽文件 function previewFile(value) {const fileExtName ${value.ext};const randFile new Date().getTime() fileExtName;uni.showLoading({title: 加載中...})wx.downloadFile({url: value.url, // 文件的本身urlfilePath: wx.env.USER_DATA_PATH / r…