java多線程之線程組與線程池,java線程和線程池,多線程的理解

本文目錄一覽:

java 線程組和線程池的作用?

一個線程的周期分為:創建、運行、銷毀三個階段。處理一個任務時,首先創建一個任務線程,然後執行任務,完了,銷毀線程。而線程處於運行狀態的時候,才是真的在處理我們交給它的任務,這個階段才是有效運行時間。所以,我們希望花在創建和銷毀線程的資源越少越好。如果不銷毀線程,而這個線程又不能被其他的任務調用,那麼就會出現資源的浪費。為了提高效率,減少創建和銷毀線程帶來時間和空間上的浪費,出現了線程池技術。這種技術是在開始就創建一定量的線程,批量處理一類任務,等待任務的到來。任務執行完畢後,線程又可以執行其他的任務。等不再需要線程的時候,就銷毀。這樣就省去了頻繁創建和銷毀線程的麻煩。

合理使用線程池以及線程變量

背景

隨着計算技術的不斷發展,3納米製程芯片已進入試產階段,摩爾定律在現有工藝下逐漸面臨巨大的物理瓶頸,通過多核處理器技術來提升服務器的性能成為提升算力的主要方向。

在服務器領域,基於java構建的後端服務器佔據着領先地位,因此,掌握java並發編程技術,充分利用CPU的並發處理能力是一個開發人員必修的基本功,本文結合線程池源碼和實踐,簡要介紹了線程池和線程變量的使用。

線程池概述

線程池是一種“池化”的線程使用模式,通過創建一定數量的線程,讓這些線程處於就緒狀態來提高系統響應速度,在線程使用完成後歸還到線程池來達到重複利用的目標,從而降低系統資源的消耗。

總體來說,線程池有如下的優勢:

線程池的使用

在java中,線程池的實現類是ThreadPoolExecutor,構造函數如下:

可以通過 new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory,handler)來創建一個線程池。

在構造函數中,corePoolSize為線程池核心線程數。默認情況下,核心線程會一直存活,但是當將allowCoreThreadTimeout設置為true時,核心線程超時也會回收。

在構造函數中,maximumPoolSize為線程池所能容納的最大線程數。

在構造函數中,keepAliveTime表示線程閑置超時時長。如果線程閑置時間超過該時長,非核心線程就會被回收。如果將allowCoreThreadTimeout設置為true時,核心線程也會超時回收。

在構造函數中,timeUnit表示線程閑置超時時長的時間單位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。

在構造函數中,blockingQueue表示任務隊列,線程池任務隊列的常用實現類有:

在構造函數中,threadFactory表示線程工廠。用於指定為線程池創建新線程的方式,threadFactory可以設置線程名稱、線程組、優先級等參數。如通過Google工具包可以設置線程池裡的線程名:

在構造函數中,rejectedExecutionHandler表示拒絕策略。當達到最大線程數且隊列任務已滿時需要執行的拒絕策略,常見的拒絕策略如下:

ThreadPoolExecutor線程池有如下幾種狀態:

線程池提交一個任務時任務調度的主要步驟如下:

核心代碼如下:

Tomcat 的整體架構包含連接器和容器兩大部分,其中連接器負責與外部通信,容器負責內部邏輯處理。在連接器中:

Tomcat為了實現請求的快速響應,使用線程池來提高請求的處理能力。下面我們以HTTP非阻塞I/O為例對Tomcat線程池進行簡要的分析。

在Tomcat中,通過AbstractEndpoint類提供底層的網絡I/O的處理,若用戶沒有配置自定義公共線程池,則AbstractEndpoint通過createExecutor方法來創建Tomcat默認線程池。

核心部分代碼如下:

其中,TaskQueue、ThreadPoolExecutor分別為Tomcat自定義任務隊列、線程池實現。

Tomcat自定義線程池繼承於java.util.concurrent.ThreadPoolExecutor,並新增了一些成員變量來更高效地統計已經提交但尚未完成的任務數量(submittedCount),包括已經在隊列中的任務和已經交給工作線程但還未開始執行的任務。

Tomcat在自定義線程池ThreadPoolExecutor中重寫了execute()方法,並實現對提交執行的任務進行submittedCount加一。Tomcat在自定義ThreadPoolExecutor中,當線程池拋出RejectedExecutionException異常後,會調用force()方法再次向TaskQueue中進行添加任務的嘗試。如果添加失敗,則submittedCount減一後,再拋出RejectedExecutionException。

在Tomcat中重新定義了一個阻塞隊列TaskQueue,它繼承於LinkedBlockingQueue。在Tomcat中,核心線程數默認值為10,最大線程數默認為200, 為了避免線程到達核心線程數後後續任務放入隊列等待,Tomcat通過自定義任務隊列TaskQueue重寫offer方法實現了核心線程池數達到配置數後線程的創建。

具體地,從線程池任務調度機制實現可知,當offer方法返回false時,線程池將嘗試創建新新線程,從而實現任務的快速響應。TaskQueue核心實現代碼如下:

Tomcat中通過自定義任務線程TaskThread實現對每個線程創建時間的記錄;使用靜態內部類WrappingRunnable對Runnable進行包裝,用於對StopPooledThreadException異常類型的處理。

Executors常用方法有以下幾個:

Executors類看起來功能比較強大、用起來還比較方便,但存在如下弊端 :

使用線程時,可以直接調用 ThreadPoolExecutor 的構造函數來創建線程池,並根據業務實際場景來設置corePoolSize、blockingQueue、RejectedExecuteHandler等參數。

使用局部線程池時,若任務執行完後沒有執行shutdown()方法或有其他不當引用,極易造成系統資源耗盡。

在工程實踐中,通常使用下述公式來計算核心線程數:

nThreads=(w+c)/c*n*u=(w/c+1)*n*u

其中,w為等待時間,c為計算時間,n為CPU核心數(通常可通過 Runtime.getRuntime().availableProcessors()方法獲取),u為CPU目標利用率(取值區間為[0, 1]);在最大化CPU利用率的情況下,當處理的任務為計算密集型任務時,即等待時間w為0,此時核心線程數等於CPU核心數。

上述計算公式是理想情況下的建議核心線程數,而不同系統/應用在運行不同的任務時可能會有一定的差異,因此最佳線程數參數還需要根據任務的實際運行情況和壓測表現進行微調。

為了更好地發現、分析和解決問題,建議在使用多線程時增加對異常的處理,異常處理通常有下述方案:

為了實現優雅停機的目標,我們應當先調用shutdown方法,調用這個方法也就意味着,這個線程池不會再接收任何新的任務,但是已經提交的任務還會繼續執行。之後我們還應當調用awaitTermination方法,這個方法可以設定線程池在關閉之前的最大超時時間,如果在超時時間結束之前線程池能夠正常關閉則會返回true,否則,超時會返回false。通常我們需要根據業務場景預估一個合理的超時時間,然後調用該方法。

如果awaitTermination方法返回false,但又希望儘可能在線程池關閉之後再做其他資源回收工作,可以考慮再調用一下shutdownNow方法,此時隊列中所有尚未被處理的任務都會被丟棄,同時會設置線程池中每個線程的中斷標誌位。shutdownNow並不保證一定可以讓正在運行的線程停止工作,除非提交給線程的任務能夠正確響應中斷。

ThreadLocal線程變量概述

ThreadLocal類提供了線程本地變量(thread-local variables),這些變量不同於普通的變量,訪問線程本地變量的每個線程(通過其get或set方法)都有其自己的獨立初始化的變量副本,因此ThreadLocal沒有多線程競爭的問題,不需要單獨進行加鎖。

ThreadLocal的原理與實踐

對於ThreadLocal而言,常用的方法有get/set/initialValue 3個方法。

眾所周知,在java中SimpleDateFormat有線程安全問題,為了安全地使用SimpleDateFormat,除了1)創建SimpleDateFormat局部變量;和2)加同步鎖 兩種方案外,我們還可以使用3)ThreadLocal的方案:

Thread 內部維護了一個 ThreadLocal.ThreadLocalMap 實例(threadLocals),ThreadLocal 的操作都是圍繞着 threadLocals 來操作的。

從JDK源碼可見,ThreadLocalMap中的Entry是弱引用類型的,這就意味着如果這個ThreadLocal只被這個Entry引用,而沒有被其他對象強引用時,就會在下一次GC的時候回收掉。

EagleEye(鷹眼)作為全鏈路監控系統在集團內部被廣泛使用,traceId、rpcId、壓測標等信息存儲在EagleEye的ThreadLocal變量中,並在HSF/Dubbo服務調用間進行傳遞。EagleEye通過Filter將數據初始化到ThreadLocal中,部分相關代碼如下:

在EagleEyeFilter中,通過EagleEyeRequestTracer.startTrace方法進行初始化,在前置入參轉換後,通過startTrace重載方法將鷹眼上下文參數存入ThreadLocal中,相關代碼如下:

EagleEyeFilter在finally代碼塊中,通過EagleEyeRequestTracer.endTrace方法結束調用鏈,通過clear方法將ThreadLocal中的數據進行清理,相關代碼實現如下:

在某權益領取原有鏈路中,通過app打開一級頁面後才能發起權益領取請求,請求經過淘系無線網關(Mtop)後到達服務端,服務端通過mtop sdk獲取當前會話信息。

在XX項目中,對權益領取鏈路進行了升級改造,在一級頁面請求時,通過服務端同時發起權益領取請求。具體地,服務端在處理一級頁面請求時,同時通過調用hsf/dubbo接口來進行權益領取,因此在發起rpc調用時需要攜帶用戶當前會話信息,在服務提供端將會話信息進行提取並注入到mtop上下文,從而才能通過mtop sdk獲取到會話id等信息。某開發同學在實現時,因ThreadLocal使用不當造成下述問題:

【問題1:權益領取失敗分析】

在權益領取服務中,該應用構建了一套高效和線程安全的依賴注入框架,基於該框架的業務邏輯模塊通常抽象為xxxModule形式,Module間為網狀依賴關係,框架會按依賴關係自動調用init方法(其中,被依賴的module 的init方法先執行)。

在應用中,權益領取接口的主入口為CommonXXApplyModule類,CommonXXApplyModule依賴XXSessionModule。當請求來臨時,會按依賴關係依次調用init方法,因此XXSessionModule的init方法會優先執行;而開發同學在CommonXXApplyModule類中的init方法中通過調用recoverMtopContext()方法來期望恢復mtop上下文,因recoverMtopContext()方法的調用時機過晚,從而導致XXSessionModule模塊獲取不到正確的會話id等信息而導致權益領取失敗。

【問題2:臟數據分析】

權益領取服務在處理請求時,若當前線程曾經處理過權益領取請求,因ThreadLocal變量值未被清理,此時XXSessionModule通過mtop SDK獲取會話信息時得到的是前一次請求的會話信息,從而造成臟數據。

【解決方案】

在依賴注入框架入口處AbstractGate#visit(或在XXSessionModule中)通過recoverMtopContext方法注入mtop上下文信息,並在入口方法的finally代碼塊清理當前請求的threadlocal變量值。

若使用強引用類型,則threadlocal的引用鏈為:Thread – ThreadLocal.ThreadLocalMap – Entry[] – Entry – key(threadLocal對象)和value;在這種場景下,只要這個線程還在運行(如線程池場景),若不調用remove方法,則該對象及關聯的所有強引用對象都不會被垃圾回收器回收。

若使用static關鍵字進行修飾,則一個線程僅對應一個線程變量;否則,threadlocal語義變為perThread-perInstance,容易引發內存泄漏,如下述示例:

在上述main方法第22行debug,可見線程的threadLocals變量中有3個threadlocal實例。在工程實踐中,使用threadlocal時通常期望一個線程只有一個threadlocal實例,因此,若不使用static修飾,期望的語義發生了變化,同時易引起內存泄漏。

如果不執行清理操作,則可能會出現:

建議使用try…finally 進行清理。

我們在使用ThreadLocal時,通常期望的語義是perThread,若不使用static進行修飾,則語義變為perThread-perInstance;在線程池場景下,若不用static進行修飾,創建的線程相關實例可能會達到 M * N個(其中M為線程數,N為對應類的實例數),易造成內存泄漏()。

在應用中,謹慎使用ThreadLocal.withInitial(Supplier? extends S supplier)這個工廠方法創建ThreadLocal對象,一旦不同線程的ThreadLocal使用了同一個Supplier對象,那麼隔離也就無從談起了,如:

總結

在java工程實踐中,線程池和線程變量被廣泛使用,因線程池和線程變量的不當使用經常造成安全生產事故,因此,正確使用線程池和線程變量是每一位開發人員必須修鍊的基本功。本文從線程池和線程變量的使用出發,簡要介紹了線程池和線程變量的原理和使用實踐,各開發人員可結合最佳實踐和實際應用場景,正確地使用線程和線程變量,構建出穩定、高效的java應用服務。

線程組和線程池的區別

線程組:

線程組存在的意義,首要原因是安全。

java默認創建的線程都是屬於系統線程組,而同一個線程組的線程是可以相互修改對方的數據的。

但如果在不同的線程組中,那麼就不能“跨線程組”修改數據,可以從一定程度上保證數據安全。

線程池:

線程池存在的意義,首要作用是效率。

線程的創建和結束都需要耗費一定的系統時間(特別是創建),不停創建和刪除線程會浪費大量的時間。所以,在創建出一條線程並使其在執行完任務後不結束,而是使其進入休眠狀態,在需要用時再喚醒,那麼 就可以節省一定的時間。

如果這樣的線程比較多,那麼就可以使用線程池來進行管理。保證效率。

線程組和線程池共有的特點:

1,都是管理一定數量的線程

2,都可以對線程進行控制—包括休眠,喚醒,結束,創建,中斷(暫停)–但並不一定包含全部這些操作。

java線程組,線程池,線程隊列分別是什麼?有什麼區別?

你好,我可以給你詳細解釋一下:

線程組表示一個線程的集合。此外,線程組也可以包含其他線程組。線程組構成一棵樹,在樹中,除了初始線程組外,每個線程組都有一個父線程組。

允許線程訪問有關自己的線程組的信息,但是不允許它訪問有關其線程組的父線程組或其他任何線程組的信息。

線程池:我們可以把並發執行的任務傳遞給一個線程池,來替代為每個並發執行的任務都啟動一個新的線程。只要池裡有空閑的線程,任務就會分配給一個線程執行。在線程池的內部,任務被插入一個阻塞隊列(Blocking Queue ),線程池裡的線程會去取這個隊列里的任務。當一個新任務插入隊列時,一個空閑線程就會成功的從隊列中取出任務並且執行它。

線程池經常應用在多線程服務器上。每個通過網絡到達服務器的連接都被包裝成一個任務並且傳遞給線程池。線程池的線程會並發的處理連接上的請求。以後會再深入有關 Java 實現多線程服務器的細節。

線程隊列:是指線程處於擁塞的時候形成的調度隊列

排隊有三種通用策略:

直接提交。工作隊列的默認選項是 SynchronousQueue,它將任務直接提交給線程而不保持它們。在此,如果不存在可用於立即運行任務的線程,則試圖把任務加入隊列將失敗,因此會構造一個新的線程。此策略可以避免在處理可能具有內部依賴性的請求集時出現鎖。直接提交通常要求無界 maximumPoolSizes 以避免拒絕新提交的任務。當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性。

無界隊列。使用無界隊列(例如,不具有預定義容量的 LinkedBlockingQueue)將導致在所有corePoolSize 線程都忙時新任務在隊列中等待。這樣,創建的線程就不會超過 corePoolSize。(因此,maximumPoolSize的值也就無效了。)當每個任務完全獨立於其他任務,即任務執行互不影響時,適合於使用無界隊列;例如,在 Web頁服務器中。這種排隊可用於處理瞬態突發請求,當命令以超過隊列所能處理的平均數連續到達時,此策略允許無界線程具有增長的可能性。

有界隊列。當使用有限的 maximumPoolSizes時,有界隊列(如 ArrayBlockingQueue)有助於防止資源耗盡,但是可能較難調整和控制。隊列大小和最大池大小可能需要相互折衷:使用大型隊列和小型池可以最大限度地降低 CPU 使用率、操作系統資源和上下文切換開銷,但是可能導致人工降低吞吐量。如果任務頻繁阻塞(例如,如果它們是 I/O邊界),則系統可能為超過您許可的更多線程安排時間。使用小型隊列通常要求較大的池大小,CPU使用率較高,但是可能遇到不可接受的調度開銷,這樣也會降低吞吐量。

什麼是java線程池?

找的資料,你看一下吧:\x0d\x0a多線程技術主要解決處理器單元內多個線程執行的問題,它可以顯著減少處理器單元的閑置時間,增加處理器單元的吞吐能力。\x0d\x0a \x0d\x0a 假設一個服務器完成一項任務所需時間為:T1 創建線程時間,T2 在線程中執行任務的時間,T3 銷毀線程時間。\x0d\x0a \x0d\x0a 如果:T1 + T3 遠大於 T2,則可以採用線程池,以提高服務器性能。\x0d\x0a 一個線程池包括以下四個基本組成部分:\x0d\x0a 1、線程池管理器(ThreadPool):用於創建並管理線程池,包括 創建線程池,銷毀線程池,添加新任務;\x0d\x0a 2、工作線程(PoolWorker):線程池中線程,在沒有任務時處於等待狀態,可以循環的執行任務;\x0d\x0a 3、任務接口(Task):每個任務必須實現的接口,以供工作線程調度任務的執行,它主要規定了任務的入口,任務執行完後的收尾工作,任務的執行狀態等;\x0d\x0a 4、任務隊列(taskQueue):用於存放沒有處理的任務。提供一種緩衝機制。\x0d\x0a \x0d\x0a 線程池技術正是關注如何縮短或調整T1,T3時間的技術,從而提高服務器程序性能的。它把T1,T3分別安排在服務器程序的啟動和結束的時間段或者一些空閑的時間段,這樣在服務器程序處理客戶請求時,不會有T1,T3的開銷了。\x0d\x0a\x0d\x0a 線程池不僅調整T1,T3產生的時間段,而且它還顯著減少了創建線程的數目,看一個例子:\x0d\x0a\x0d\x0a 假設一個服務器一天要處理50000個請求,並且每個請求需要一個單獨的線程完成。在線程池中,線程數一般是固定的,所以產生線程總數不會超過線程池中線程的數目,而如果服務器不利用線程池來處理這些請求則線程總數為50000。一般線程池大小是遠小於50000。所以利用線程池的服務器程序不會為了創建50000而在處理請求時浪費時間,從而提高效率。

原創文章,作者:AZZZM,如若轉載,請註明出處:https://www.506064.com/zh-hant/n/330383.html

(0)
打賞 微信掃一掃 微信掃一掃 支付寶掃一掃 支付寶掃一掃
AZZZM的頭像AZZZM
上一篇 2025-01-16 15:46
下一篇 2025-01-16 15:46

相關推薦

  • Java JsonPath 效率優化指南

    本篇文章將深入探討Java JsonPath的效率問題,並提供一些優化方案。 一、JsonPath 簡介 JsonPath是一個可用於從JSON數據中獲取信息的庫。它提供了一種DS…

    編程 2025-04-29
  • java client.getacsresponse 編譯報錯解決方法

    java client.getacsresponse 編譯報錯是Java編程過程中常見的錯誤,常見的原因是代碼的語法錯誤、類庫依賴問題和編譯環境的配置問題。下面將從多個方面進行分析…

    編程 2025-04-29
  • Java騰訊雲音視頻對接

    本文旨在從多個方面詳細闡述Java騰訊雲音視頻對接,提供完整的代碼示例。 一、騰訊雲音視頻介紹 騰訊雲音視頻服務(Cloud Tencent Real-Time Communica…

    編程 2025-04-29
  • Java Bean加載過程

    Java Bean加載過程涉及到類加載器、反射機制和Java虛擬機的執行過程。在本文中,將從這三個方面詳細闡述Java Bean加載的過程。 一、類加載器 類加載器是Java虛擬機…

    編程 2025-04-29
  • Java Milvus SearchParam withoutFields用法介紹

    本文將詳細介紹Java Milvus SearchParam withoutFields的相關知識和用法。 一、什麼是Java Milvus SearchParam without…

    編程 2025-04-29
  • Java 8中某一周的周一

    Java 8是Java語言中的一個版本,於2014年3月18日發布。本文將從多個方面對Java 8中某一周的周一進行詳細的闡述。 一、數組處理 Java 8新特性之一是Stream…

    編程 2025-04-29
  • Java判斷字符串是否存在多個

    本文將從以下幾個方面詳細闡述如何使用Java判斷一個字符串中是否存在多個指定字符: 一、字符串遍歷 字符串是Java編程中非常重要的一種數據類型。要判斷字符串中是否存在多個指定字符…

    編程 2025-04-29
  • VSCode為什麼無法運行Java

    解答:VSCode無法運行Java是因為默認情況下,VSCode並沒有集成Java運行環境,需要手動添加Java運行環境或安裝相關插件才能實現Java代碼的編寫、調試和運行。 一、…

    編程 2025-04-29
  • Python多線程讀取數據

    本文將詳細介紹多線程讀取數據在Python中的實現方法以及相關知識點。 一、線程和多線程 線程是操作系統調度的最小單位。單線程程序只有一個線程,按照程序從上到下的順序逐行執行。而多…

    編程 2025-04-29
  • Java任務下發回滾系統的設計與實現

    本文將介紹一個Java任務下發回滾系統的設計與實現。該系統可以用於執行複雜的任務,包括可回滾的任務,及時恢復任務失敗前的狀態。系統使用Java語言進行開發,可以支持多種類型的任務。…

    編程 2025-04-29

發表回復

登錄後才能評論