分類
發燒車訊

中國四部委再次要求 破除新能源車地方保護

中國財政部等四部委近日聯合公布了第二批新能源汽車推廣應用城市名單,公告再次明確提出了破除新能源汽車地方保護的要求。業內人士表示,京滬兩大新能源汽車推廣主力城市需要拿出姿態,以警示後續的地方保護行為。

去(2013)年9月17日,財政部曾在公布的新一輪新能源汽車補貼政策中明確指出,示范城市或區域推廣應用的車輛中外地品牌數量不得低於30%,且不得設置或變相設置障礙限制採購外地品牌車輛。

隨著國家扶持新能源汽車相關政策不斷出台,北京、上海、天津等地紛紛打造當地新能源汽車產業,考慮到利於當地企業的發展,在引入外非本地企業的新能源汽車品牌時,會制定一些差異化條款,比如優先購買本地新能源汽車等。

因此在國家推薦的節能與新能源汽車產品目錄之下,地方政府還設定了一個地方性的產品目錄。其中,北京與上海市場針對外地新能源汽車品牌皆有相應的條件要求。

重慶、呼和浩特等地也紛紛提出,採購和補貼的對象是符合當地政府認定的新能源汽車。天津、河南等地在節能與新能源汽車採購對象上也明確規定,在技術、服務等指標滿足採購需要的前提下,優先採購納入政府採購范圍的電動汽車。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

分類
發燒車訊

搶先日產 比亞迪全電動計程車隊進入倫敦

中國汽車廠商比亞迪(BYD)今(11)日宣布,首支英國倫敦史上全電動計程車隊正式上路,在2018年前批量供應零排放出租車的競爭中,搶在了日產(Nissan)等國際競爭對手之前。不到2個月前,比亞迪還交付了倫敦史上首批全電動公共汽車。

據《金融時報》報導,倫敦市長鮑里斯約翰遜(Boris Johnson)設定了全市計程車必須在2018年前實現零排放的目標,引發汽車廠商爭相開發新車。

比亞迪趕在該期限之前率先打入了倫敦交通市場。比亞迪將推出20輛電動汽車組成的車隊,由出租車公司Thriev營運。

另一方面,日本電動車廠商日產(Nissan)與英國經典黑出租車製造商倫敦出租車公司(London Taxi Company) ,也準備趕在2018年期限之前開發出全電動車型。

著名的股神華倫•巴菲特(Warren Buffett)持有比亞迪9.9%股份。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

分類
發燒車訊

曠世提出類別正則化的域自適應目標檢測模型,緩解場景多樣的痛點 | CVPR 2020

論文基於DA Faster R-CNN系列提出類別正則化框架,充分利用多標籤分類的弱定位能力以及圖片級預測和實例級預測的類一致性,從實驗結果來看,類該方法能夠很好地提升DA Faster R-CNN系列的性能

來源:曉飛的算法工程筆記 公眾號

論文: Exploring Categorical Regularization for Domain Adaptive Object Detection

  • 論文地址:https://arxiv.org/pdf/2003.09152.pdf
  • 論文代碼:https://github.com/Megvii-Nanjing/CR-DA-DET

Introduction

  由於標註成本大,在訓練好檢測算法后,面對差異較大的新場景(類別不變),若想獲取大量的帶標註圖片進行再訓練是很不方便的。對於這種情況,無監督的域自適應方法能夠靈活地自適應新場景,從包含豐富標註信息的源域轉移到無標註的目標域。其中,域自適應方法中比較有代表性的是Donamin Adaptive(DA) Faster R-CNN系列,利用對抗訓練來對齊圖片和實例的分佈,使得模型能夠做到域不變性,具體可以看上一篇介紹。
  但是這些方法大都把無法轉化的背景內容也進行了對齊,而且在實例對齊時,沒有從包含較多低質量的proposal集合中識別出難樣本。為了解決上面的問題,論文提出類別正則化框架,幫助DA Faster R-CNN專註於對齊跨域中的關鍵區域和重要目標。
  論文的主要貢獻如下:

  • 提出新的類別正則化框架,作為域自適應目標檢測算法的插件,不需要額外的標註和超參數。
  • 設計了兩個正則化模塊,分別用於榨取卷積分類器的弱定位能力以及圖像級別預測和實例級別預測間的類別一致性,能夠幫助分類器專註於對齊目標相關區域以及難對齊實例。
  • 對多種域轉移場景進行實驗,驗證論文提出的方法的有效性。從實驗結果來看,類別正則化框架能夠提出DA Faster R-CNN系列方法的性能,並在基礎數據集上達到SOTA。

Approach

Framework Overview

  論文方法的整體架構如圖2,在DA Faster R-CNN基礎上添加了ICR(image-level categorical regularization)和CCR(categorical consistency regularization),能夠更好地對齊域間的關鍵區域和重要實例。

Image-Level Categorical Regularization

  ICR的主要目的是提高主幹網絡的目標特徵提取能力,同時降低背景的激活。結構如圖2b所示,ICR使用源域數據進行有監督訓練,對主幹網絡的特徵輸出進行全局池化,再使用多標籤分類器($1\times 1$卷積)進行分類,損失函數使用標準交叉熵多標籤損失:

  $C$為類別總數,$yc$為GT標籤,$\hat{y}c$為預測標籤,$y^c=1$表示圖片至少包含一個類別$c$物體。

 ICR模塊利用多標籤分類器的弱定位能力,能夠有監督地引導主幹網絡只激活類相關特徵。如圖3所示,類相關的特徵會有較高的激活值。在圖像級對齊時,能夠對齊域間關鍵區域,同時,由於背景沒有參与到圖像級多標籤分類器中,能夠有效減少擬合不可對齊的源背景的可能性。

Categorical Consistency Regularization

  CCR負責發現難對齊實例,調整實例級對齊損失的權重,基於兩點考慮:

  • 由於不能區分前景和後景,實例對齊模塊可能被低質量背景proposal佔據。
  • 添加的圖像級分類器和實例檢測head是互補的,前者負責獲取所有圖像級上下文信息,後者使用精確的RoI特徵,當兩者預測不一致時,該實例就是難樣本。

  基於以上考慮,論文採用圖像級預測和實例級預測的類別一致性作為目標分類難易程度的判斷,並在目標域中使用該一致性作為正則因子,調節難對齊樣本在實例對齊中的權重。假定$\hat{p}{c}_j$為預測第$j$個實例為類別$c$的概率,$\hat{y}c$為實例預測包含類別$c$的概率,類別一致性的計算為

  使用公式5來加權實例級對抗損失

  需要注意,僅對目標域的檢測head預測為前景的實例使用公式5加權,源域的所有實例和目標域的背景實例均使用$d_j=1$,前者因為是有監督的,而後者則是因為不重要。

Integration with DA Faster R-CNN Series

  將論文提出的方法加入到DA Faster R-CNN中,ICR為直接加入,CCR為對原損失的修改,最終的損失函數為

  論文也對比了另外一種主流的DA -Faster改進SW-Faster,該方法使用弱全局對齊模型來提升DA-Faster的強圖像對齊模塊,直接加入ICR和CCR,最終的損失函數為

Experiments

Comparison Results

  Faster R-CNN(Source)僅使用源域訓練,Faster R-CNN(Oracle)僅使用目標域訓練。

  • Weather Adaptation

  這裏對比模型對天氣的自適應性。

  • Scene Adaptation

  這裏對比模型對不同城市的場景的自適應性。

  • Dissimilar Domain Adaptation

  這裏對比模型對真實圖片和卡通圖片的自適應性。

Visualization and Analyses

  對前面對比實驗的目標域測試圖片進行了可視化。

  將特徵降維並可視化,藍點為源域樣本,紅點為目標域樣本,可以看到論文的方法能夠讓域間的同分類實例距離更近。
  論文也計算了域間距離,使用Earth Movers Distance (EMD) 測量,SW-Faster, SW-Faster-ICR and SW-FasterICR-CCR的結果分別是8.84、8.59和8.15。

CONCLUSION

  論文基於DA Faster R-CNN系列提出類別正則化框架,充分利用多標籤分類的弱定位能力以及圖片級預測和實例級預測的類一致性,從實驗結果來看,類該方法能夠很好地提升DA Faster R-CNN系列的性能。



如果本文對你有幫助,麻煩點個贊或在看唄~
更多內容請關注 微信公眾號【曉飛的算法工程筆記】

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

分類
發燒車訊

深入理解進程,線程,協程

今日得到

  • 計算機科學領域的任何問題都可以通過增加一個間接的中間層來解決

  • 併發:Do not communicate by sharing memory; instead, share memory by communicate. (不要以共享內存的方式來通信,相反,要通過通信來共享內存)

1. 進程

進程是系統進行資源分配和調度的一個獨立單位,程序段、數據段、PCB三部分組成了進程實體(進程映像),PCB是進程存在的唯一標準

1.1 進程的組織方式:

  • 鏈接方式
    • 按照進程狀態將PCB分為多個隊列,就緒隊列,阻塞隊列等
    • 操作系統持有指向各個隊列的指針
  • 索引方式
    • 根據進程狀態的不同,建立幾張索引表
    • 操作系統持有指向各個索引表的指針

1.2 進程的狀態

  • 創建態: 操作系統為進程分配資源,初始化PCB

  • 就緒態:運行資源等條件都滿足,存儲在就緒隊列中,等待CPU調度

  • 運行態:CPU正在執行進程

  • 阻塞態:等待某些條件滿足,等待消息回復,等待同步鎖,sleep等,阻塞隊列

  • 終止態 :回收進程擁有的資源,撤銷PCB

1.3 進程的切換和調度

進程在操作系統內核程序臨界區中不能進行調度與切換

臨界資源:一個時間段內只允許一個進程使用資源,各進程需要互斥地訪問臨界資源

臨界區:訪問臨界資源的代碼

內核程序臨界區:訪問某種內核數據結構,如進程的就緒隊列(存儲各進程的PCB)

進程調度的方式:

  • 非剝奪調度方式(非搶佔方式),只允許進程主動放棄處理機,在運行過程中即便有更緊迫的任務到達,當前進程依然會繼續使用處理機,直到該進程終止或者主動要求進入阻塞態
  • 剝奪調度方式(又稱搶佔方式)當一個進程正在處理機上執行時,如果有一個優先級更高的進程需要處理機,則立即開中斷暫停正在執行的進程,將處理機飯呢陪給優先級高的那個進程

進程的切換與過程:進程的調度、切換是有代價的

  1. 對原來運行進程各種數據的保存
  2. 對新的進程各種數據恢復(程序計數器,程序狀態字,各種數據寄存器等處理機的現場)

進程調度算法的相關參數:

  • CPU利用率:CPU忙碌時間/作業完成的總時間
  • 系統吞吐量:單位時間內完成作業的數量
  • 周轉時間:從作業被提交給系統開始,到作業完成為止的時間間隔 = 作業完成時間-作業提交時間
  • 帶權周轉時間:(由於周轉時間相同的情況下,可能實際作業的運行時間不一樣,這樣就會給用戶帶來不一樣的感覺) 作業周轉時間/作業實際運行時間, 帶權周轉時間>=1, 越小越好
  • 平均帶權周轉時間:各作業帶權周轉時間之和/作業數
  • 等待時間
  • 響應時間

調度算法:

算法思想,用於解決什麼問題?

算法規則,用於作業(PCB作業)調度還是進程調度?

搶佔式還是非搶佔式的?

優缺點?是否會導致飢餓?

以下調度算法是適用於當前交互式操作系統

  • 時間片輪轉(Round-Robin)
    • 算法思想:公平地、輪流地為各個進程服務,讓每個進程在一定時間間隔內可以得到相應
    • 算法規則:按照各進程到達就緒隊列的順序,輪流讓各個進程執行一個時間片(如100ms)。若進程未在一個時間片內執行完,則剝奪處理機,將進程重新放到就緒隊列隊尾重新排隊。
    • 用於作業/進程調度:用於進程的調度(只有作業放入內存建立相應的進程后,才會被分配處理機時間片)
    • 是否可搶佔?若進程未能在規定時間片內完成,將被強行剝奪處理機使用權,由時鐘裝置發出時鐘中斷信號來通知CPU時間片到達
    • 優缺點:適用於分時操作系統,由於高頻率的進程切換,因此有一定開銷;不區分任務的緊急程度
    • 是否會導致飢餓? 不會
  • 優先級調度算法
    • 算法思想:隨着計算機的發展,特別是實時操作系統的出現,越來越多的應用場景需要根據任務的進程成都決定處理順序
    • 算法規則:每個作業/進程有各自的優先級,調度時選擇優先級最高的作業/進程
    • 用於作業/進程調度:即可用於作業調度(處於外存後備隊列中的作業調度進內存),也可用於進程調度(選擇就緒隊列中的進程,為其分配處理機),甚至I/O調度
    • 是否可搶佔? 具有可搶佔版本,也有非搶佔式的
    • 優缺點:適用於實時操作系統,用優先級區分緊急程度,可靈活地調整對各種作業/及進程的偏好程度。缺點:若源源不斷地提供高優先級進程,則可能導致飢餓
    • 是否會導致飢餓: 會
  • 多級反饋隊列調度算法
    • 算法思想:綜合FCFS、SJF(SPF)、時間片輪轉、優先級調度

    • 算法規則:

      • 1.設置多級就緒隊列,各級別隊列優先級從高到底,時間片從小到大
      • 2.新進程到達時先進入第1級隊列,按照FCFS原則排隊等待被分配時間片,若用完時間片進程還未結束,則進程進入下一級隊列隊尾
      • 3.只有第k級別隊列為空時,才會為k+1級對頭的進程分配時間片
    • 用於作業/進程調度:用於進程調度

    • 是否可搶佔? 搶佔式算法。在k級隊列的進程運行過程中,若更上級別的隊列(1-k-1級)中進入一個新進程,則由於新進程處於優先級高的隊列中,因此新進程會搶佔處理機,原理運行的進程放回k級隊列隊尾。

    • 優缺點:對各類型進程相對公平(FCFS的有點);每個新到達的進程都可以很快就得到相應(RR優點);短進程只用較少的時間就可完成(SPF)的有點;不必實現估計進程的運行時間;可靈活地調整對各類進程的偏好程度,比如CPU密集型進程、I/O密集型進程(拓展:可以將因I/O而阻塞的進程重新放回原隊列,這樣I/O型進程就可以保持較高優先級)

    • 是否會導致飢餓: 會

2. 線程

引入線程之後,進程只作為除CPU之外的系統資源的分配單元(如:打印機,內存地址空間等都是分配給進程的)

線程的是實現方式:

  • 用戶級線程(User-Level Thread),用戶級線程由應用程序通過線程庫是實現如python (import thread), 線程的管理工作由應用程序負責。
  • 內核級線程(kernel-Level Thread),內核級線程的管理工作由操作系統內核完成,線程調度,切換等工作都由內核負責,因此內核級線程的切換必然需要在核心態下才能完成

進程和線程的關係:一條線程指的是進程中一個單一順序的控制流,一個進程中可以併發多個線程,每條線程并行執行不同的任務。CPU的最小調度單元是線程,所以單進程多線程是可以利用多核CPU的。

2.1 線程模型:

  • 用戶級線程模型(一對多模型)

多個用戶態的線程對應着一個內核線程,程序線程的創建、終止、切換或者同步等線程工作必須自身來完成。python就是這種。雖然可以實現異步,但是不能有效利用多核(GIL)

  • 內核級線程模型 (一對一)

這種模型直接調用操作系統的內核線程,所有線程的創建、終止、切換、同步等操作,都由內核來完成。C++就是這種

  • 兩級線程模型(M:N)

這種線程模型會先創建多個內核級線程,然後用自身的用戶級線程去對應創建的多個內核級線程,自身的用戶級線程需要本身程序去調度,內核級的線程交給操作系統內核去調度。GO語言就是這種。

python中的多線程因為GIL的存在,並不能利用多核CPU優勢,但是在阻塞的系統調用中,如sock.connect(), sock.recv()等耗時的I/O操作,當前的線程會釋放GIL,讓出處理器。但是單個線程內,阻塞調用上還是阻塞的。除了GIL之外,所有的多線程還有通病,他們都是被OS調用的,調度策略是搶佔式的,以保證同等有限級的線程都有機執行,帶來的問題就是:並不知道下一刻執行那個線程,也不知道正在執行什麼代碼,會存在競態條件

3. 協程

協程通過在線程中實現調度,避免了陷入內核級別的上下文切換造成的性能損失,進而突破了線程在IO上的性能瓶頸。

python的協程源於yield指令

  • yield item 用於產出一個值,反饋給next()的調用方法
  • 讓出處理機,暫停執行生成器,讓調用方繼續工作,直到需要使用另一個值時再調用next()

協程式對線程的調度,yield類似惰性求職方式可以視為一種流程控制工具,實現協作式多任務,python3.5引入了async/await表達式,使得協程證實在語言層面得到支持和優化,大大簡化之前的yield寫法。線程正式在語言層面得到支持和優化。線程是內核進行搶佔式調度的,這樣就確保每個線程都有執行的機會。而coroutine運行在同一個線程中,有語言層面運行時中的EventLoop(事件循環)來進行調度。在python中協程的調度是非搶佔式的,也就是說一個協程必須主動讓出執行機會,其他協程才有機會運行。讓出執行的關鍵字 await, 如果一個協程阻塞了,持續不讓出CPU處理機,那麼整個線程就卡住了,沒有任何併發。

PS: 作為服務端,event loop最核心的就是I/O多路復用技術,所有來自客戶端的請求都由I/O多路復用函數來處理;作為客戶端,event loop的核心在於Future對象延遲執行,並使用send函數激發協程,掛起,等待服務端處理完成返回后再調用Callback函數繼續執行。[python 協程與go協程的區別]

3.1 Golang 協程

Go 天生在語言層面支持,和python類似都是用關鍵字,而GO語言使用了go關鍵字,go協程之間的通信,採用了channel關鍵字。

go實現了兩種併發形式:

  • 多線程共享內存:如Java 或者C++在多線程中共享數據的時候,通過鎖來訪問
  • Go語言特有的,也是Go語言推薦的 CSP(communicating sequential processes)併發模型。
package main 

import ("fmt")

func main() {
    jobs := make(chan int)
    done := make(chan bool)  // end flag
    
    go func() {
        for {
            j, ok := <- jobs 
            fmt.Println("---->:", j, ok)
            if ok {
                fmt.Println("received job")
            } else {
                fmt.Println("end received jobs")
                done <- true
                return
            }
        }
    }()
    
    go func() {
        for j:= 1; j <= 3; j++ {
            jobs <-j
            fmt.Println("sent job", j)
        }
        close(jobs)
        fmt.Println("close(jobs)")
    }()
    
    fmt.Println("sent all jobs")
    <-done  // 阻塞 讓main等待協程完成
}

Go的CSP併發模型是通過goroutine 和 channel來實現的。

  • goroutine是go語言中併發的執行單位。
  • channel是Go語言中各個併發結構體之間的通信機制。
    • channel -< data 寫數據
    • <- channel 讀數據

協程本質上來說是一種用戶態的線程,不需要系統來執行搶佔式調度,而是在語言測個面實現線程的調度。

4. 併發

併發:Do not communicate by sharing memory; instead, share memory by communicate.

4.1 Actor模型

Actor模型和CSP模型的區別:

  • CSP並不Focus發送消息的實體/Task, 而是關注發送消息時消息所使用的載體,即channel。
  • 在Actor的設計中,Actor與信箱是耦合的,而在CSP中channel是作為first-class獨立存在的
  • Actor中有明確的send/receive關係,而channel中並不區分這樣的關係,執行快可以任意選擇發送或者取消息

好文推薦:Go/Python/Erlang編程語言對比分析及示例

4.4 Go 協程調度器 GPM

  • G 指的是Goroutine,其本質上也是一種輕量級的線程
  • P proessor, 代表M所需要的上下文環境,也是處理用戶級代碼邏輯處理器。同一時間只有一個線程(M)可以擁有P, P中的數據都是鎖自由(lock free)的, 讀寫這些數據的效率會非常的高
  • M Machine,一個M直接關聯一個內核線程,可以運行go代碼 即goroutine, M運行go代碼需要一個P, 另外就是運行原生代碼,如 syscall。運行原生代碼不需要P。

一個M會對應一個內核線程,一個M也會連接一個上下文P,一個上下文P相當於一個“處理器”,一個上下文連接一個或者多個Goroutine。P(Processor)的數量是在啟動時被設置為環境變量GOMAXPROCS的值,或者通過運行時調用函數runtime.GOMAXPROCS()進行設置

erlang和golang都是採用CSP模型,python中協程是eventloop模型。但是erlang是基於進程的消息通信,go是基於goroutine和channel通信。

python和golang都引入了消息調度系統模型,來避免鎖的影響和進程線程的開銷問題。

計算機科學領域的任何問題都可以通過增加一個間接的中間層來解決 — G-P-M模型正是此理論踐行者,此理論也用到了python的asyncio對地獄回調的處理上(使用Task+Future避免回調嵌套),是不是巧合?
其實異步≈可中斷的函數+事件循環+回調,go和python都把嵌套結構轉換成列表結構有點像算法中的遞歸轉迭代.

調度器在計算機中是分配工作時所需要的資源,Linux的調度是CPU找到可運行的線程,Go的調度是為M線程找到P(內存,執行票據)和可運行的G(協程)

Go協程是輕量級的,棧初始2KB(OS操作系統的線程一般都是固有的棧內存2M), 調度不涉及系統調用,用戶函數調用前會檢查棧空間是否足夠,不夠的話,會進行站擴容,棧大小限制可以達到1GB。

Go的網絡操作是封裝了epoll, 為NonBlocking模式,切換協程不阻塞線程。

Go語言相比起其他語言的優勢在於OS線程是由OS內核來調度的,goroutine則是由Go運行時(runtime)自己的調度器調度的,這個調度器使用一個稱為m:n調度的技術(復用/調度m個goroutine到n個OS線程)。 其一大特點是goroutine的調度是在用戶態下完成的, 不涉及內核態與用戶態之間的頻繁切換,包括內存的分配與釋放,都是在用戶態維護着一塊大的內存池, 不直接調用系統的malloc函數(除非內存池需要改變),成本比調度OS線程低很多。 另一方面充分利用了多核的硬件資源,近似的把若干goroutine均分在物理線程上, 再加上本身goroutine的超輕量,以上種種保證了go調度方面的性能。點我了解更多

4.5 Go 調度器的實現 以及搶佔式調度

legendtkl阿里雲技術專家

Golang源碼探索(二) 協程的實現原理

相關參考文獻:

王道操作系統

操作系統中調度算法(FCFS、RR、SPN、SRT、HRRN)

Python協程與Go協程的區別二

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

分類
發燒車訊

特斯拉今年或沿京滬高速布局充電設施

據阿思達克財經報導,特斯拉產品專家表示,特斯拉預計第二或者第三季度在上海開店,同時,特斯拉在中國的充電設施首先會沿著京滬高速布局,只是具體布局時間暫不確定。

上述人士表示,特斯拉(Tesla)中國客戶現在訂車,將於今年年底拿到車。上海客戶屆時則可以直接在上海提車,不需要自己出錢把車從北京提到上海。另外,特斯拉客戶可以用自己的燃油車車牌置換,或者參與上海市拍牌。

據介紹,特斯拉在中國大陸目前有6輛,北京店有3輛上牌車,還有3輛試駕車。

針對客戶關心的充電問題,該產品專家表示,客戶購買特斯拉的車價裡面已經包含了充電樁費用,消費者不會再另外花錢購買充電樁,隻需要再支付從物業拉電到車位的材料、人工等費用。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

分類
發燒車訊

新技術如何驅動混合動力汽車市場化-第三屆混合動力汽車技術峰會

在中國日益嚴重的環境污染壓力和“霧霾”陰影籠罩下,混合動力及純電動汽車的發展形成了勢在必行的趨勢。即使《節能與新能源汽車產業發展規劃》以及四部委聯合發佈的《關於繼續開展新能源汽車推廣應用工作的通知》中明確了純電動以及插電式混合動力汽車的補貼政策,第二批新能源汽車試點運行城市也出臺在即,但新能源汽車產業的前景仍然未能明朗化,標準不統一,基礎建設不健全,電池技術受到局限的電動車以及補貼政策不明確的混合動力汽車都面臨各自發展的瓶頸。在全球並未形成一種成熟應用模式的狀況下,中國應該走出怎樣的一條有自我特色的路線圖。

目前,全球能源和環境系統面臨巨大的挑戰,汽車作為石油消耗和二氧化碳排放的大戶,需要進行革命性的變革。目前全球新能源汽車發展已經形成了共識,從長期來看,包括純電動、燃料電池技術在內的純電驅動將是新能源汽車的主要技術方向,在短期內,油電混合、插電式混合動力將是重要的過渡路線。目前來看,全球新能源汽車的發展還面臨著一些共同的難題,例如關鍵技術的突破、汽車工業的轉型、基礎設施的建設以及消費者的接受度等。

為了提升中國混合動力汽車技術發展,幫助整車廠獲得一手的技術和市場訊息,第三屆混合動力汽車技術峰會茲定於2014年6月26日至27日,在虹橋喜來登上海太平洋大飯店舉行,主題為“技術驅動混合動力汽車市場化”。

市場方面,中國汽車技術研究中心的高級工程師張銅柱將在此次會議上對電動汽車分標委工作及標準最新制修訂情況做相關發言,介紹工作組成立背景、工作組籌備情況、成員構成、工作內容及工作計畫等。中國化學與物理電源行業協會副秘書長劉彥龍將會就新能源汽車用動力電池市場需求發表演講,深度分析目前國內外鋰電池的發展處於一個什麼狀態,決定鋰電池未來應用之路的關鍵是什麼。中國機電產品進出口商會汽車分會秘書長孟凡一將會對中國汽車進出口情況進行回顧及展望,並分析在全球經濟活動較弱以及國際貿易保護主義抬頭的態勢下,2014年國際汽車市場的總體形勢和客觀上嚴重影響我國汽車出口行業的諸多不確定因素。

技術方面,精進電動創始人兼CTO蔡蔚將會對汽車電動化的動力總成與動力總成的電機系統做相關介紹,講述他觀點中混合動力汽車長期存在的基礎,動力總成的拓撲結構,混合動力變速箱的分析與比較,以及動力總成對電機系統要求與電機系同的開發及產業化。上海電驅動股份有限公司技術中心主任徐延東將結合領先的Demo project解析混合動力電機控制器集成裝置及其技術解決方案。其他技術題目還包括底盤,控制器軟硬體,逆變器,插電式混合動力,增程式混合動力汽車以及超級電容等領域的領先技術。

歷屆混合動力汽車技術峰會得到了發改委能源研究所、中國汽車工業協會專家委員會、上海汽車工程學會、中國汽車技術研究中心、中國電源產業創新聯盟、上海交通大學汽車工程研究院、汽車安全與節能國家重點實驗室、中國北方車輛研究所動力電池實驗室等組織的大力支持。

更多詳情請聯繫第三屆混合動力汽車技術峰會組委會
朱小姐電話:+86 21 61808505*212
郵箱:

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

分類
發燒車訊

未來3到4年 Tesla規劃在中國生產電動汽車

特斯拉(Tesla)執行長 Elon Musk 昨(21) 日在華盛頓 Geekpark 大會上表示,公司正準備交付首批S型號的電動車給中國的客戶。Tesla 也正在中國佈建“龐大”的充電站網路,包括在北京及上海的超級充電站。Musk預期,未來 3 ~4 年內,Tesla 公司將開始在中國生產電動汽車。

在中國本土生產可避免繳付中國25%的入口關稅,因此可讓公司降低電動車售價。Tesla 公司預測,最快在明年,其在中國市場的銷售量將追平在美國的銷量。

配備85千瓦時電池的S型號電動車,在中國的售價為73.4萬元人民幣(約11.8萬美元),遠高於在美國未計聯邦稅務寬減前的售價約為7.1萬美元,因為加入了運費、增值稅及入口稅。

該公司自去年8月開始接受中國客戶的訂購,並在去年底於北京一個商場開設了一個800平方米的銷售點,展示其電動車。

中國商務部:今年是電動汽車消費元年

中國政府竭盡所能想讓人們購買使用電動汽車,以改善中國的空氣污染問題,而 Musk 相信,Tesla 能幫助中國政府達成這一目標。

中國商務部研究院消費經濟研究部主任趙萍近日在《2014年消費市場發展報告》新聞發布會上表示,今年將是電動汽車消費元年。政策、產品、設施「三位一體」協同推進,意味著今年將迎來中國汽車電子市場的蓬勃發展。

趙萍指出,今年限購或將席捲更多城市,使汽車消費在一二線主要城市遭遇到行政力量預製的「天花板」。由於空氣污染等環境壓力,中國汽車市場的產銷趨勢將存在較大的不確定性,自主品牌也將遭受到更大的挑戰。佔社會消費品零售總額比重高達1/4的汽車消費增速放緩,將會降低總消費增長對汽車的過度依賴。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

分類
發燒車訊

雷諾計畫2016年啟動在華生産 純電動汽車也在考慮之列

據日經新聞今(23)日報導,法國車企雷諾(Renault)將從2016年開始啟動在華生産,並與組成聯盟的日産汽車(Nissan)在採購和生産等廣泛領域展開合作,通過共享日産的採購網等舉措來削減成本,以提高價格競爭力。值得注意的是,雷諾還將考慮在中國生産純電動汽車(EV)。

在正在舉行的北京國際車展上,雷諾亞太區董事長吉爾斯•諾曼德(Gilles Normand)表示,「(在中國)起步較晚的雷諾要想脫穎而出,如何推進本地化生産非常重要」。

雷諾將積極利用日産的採購網等,將零部件的本地採購率提高至85%左右。德國福斯(VW)和美國通用汽車(GM)等主要企業已經進駐中國,而本地採購率被認為達到80~90%。

雷諾去年12月從中國政府獲得了工廠建設許可,距離其計劃發佈已有9年。諾曼德表示,亞太地區汽車銷量佔世界的45%,而雷諾全球份額中僅有約10%來自亞太,「希望提高存在感」。

雷諾在中國已經與東風汽車建立了合資公司。工廠位於湖北省武漢市,將於2016年開始生産多功能運動車(SUV)等,年産能在15萬輛左右。該公司首席執行官(CEO)卡洛斯•戈恩表示,將來希望擴大至年産60~70萬輛。

中國目前面臨著嚴重的大氣污染問題。諾曼德表示,「在中國無法避開環境問題」,針對純電動車的生産,「公司內部正在進行各方面討論」,承認正在考慮純電動車的本地化生産。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

分類
發燒車訊

java併發之volatile關鍵字

Java面試中經常會涉及關於volatile的問題。本文梳理下volatile關鍵知識點。

volatile字意為“易失性”,在Java中用做修飾對象變量。它不是Java特有,在C,C++,C#等編程語言也存在,只是在其它編程語言中使用有所差異,但總體語義一致。比如使用volatile 能阻止編譯器對變量的讀寫優化。簡單說,如果一個變量被修飾為volatile,相當於告訴系統說我容易變化,編譯器你不要隨便優化(重排序,緩存)我。

Happens-before

規範上,Java內存模型遵行happens-before

volatile變量在多線程中,寫線程和讀線程具有happens-before關係。也就是寫值的線程要在讀取線程之前,並且讀線程能完全看見寫線程的相關變量。

happens-before:如果兩個有兩個動作AB,A發生在B之前,那麼A的順序應該在B前面並且A的操作對B完全可見。

happens-before 具有傳遞性,如果A發生在B之前,而B發生在C之前,那麼A發生在C之前。

如何保證可見性

多線程環境下counter變量的更新過程。線程1先從主存拷貝副本到CPU緩存,然後CPU執行counter=7,修改完后寫入CPU緩存,等待時機同步到主存。在線程1同步主存前,線程2讀到counter值依然為0。此時已經發生內存一致性錯誤(對於相同的共享數據,多線程讀到視圖不一致)。因為線程2看不見線程1操作結果,也將這個問題稱為可見性問題

public class SharedObject {
    public int counter = 0;
}

因為多了緩存優化導致,導致可見性問題。所以volatile通過消除緩存(描述可能不太準確)來避免。例如當使用volatile修飾變量后,操作該變量讀寫直接與主存交互,跳過緩存層,保證其它讀線程每次獲取的都是最新值。

    public volatile int counter = 0;

volatile 不單隻消除修飾的變量的緩存。事實上與之相關的變量在讀寫時也會消除緩存,如同使用了volatile一樣。

如下 years,months,days 三個變量中只有days是volatile,但是對years,months讀寫操作也和days時也會跳過緩存,其它線程每次讀到的都是最新值。

public class MyClass {
    private int years;
    private int months
    private volatile int days;
    public int totalDays() {
        int total = this.days;
        total += months * 30;
        total += years * 365;
        return total;
    }
    public void update(int years, int months, int days){
        this.years  = years;
        this.months = months;
        this.days   = days;
    }
}

這是為什麼?我們分析一下。

一個寫線程調用 update,讀線程調用totalDays。單線程中,對於update方法,wa與wb存在happens-before關係, wawb 之前執行並對wb可見。

多線程中rc與wb存在happens-before關係,wbrc之前執行並對rc可見。根據 happens-before傳遞性,wa需要在rc前先執行並對rc可見。

因為wb是volatile變量,所以rc獲取的years,months也是最新值。

我們知道出於性能原因,JVM和CPU會對程序中的指令進行重新排序。如果update方法裏面wawb順序被重排,那它們的happens-before關係將不在成立。

為了避免這個問題,volatile對重排序做了保證 對於發生在volatile變量操作前的其他變量的操作不能重新排序

由此我們得到volatile通過消除緩存防止重排保證線程的可見性。

volatile保證線程安全?

討論線程安全,大家都會提及原子性順序性可見性。volatile側重於保證可見性,也就是當寫的線程更新后,讀線程總能獲得最新值。在只有一個線程寫,多個線程讀的場景下,volatile能滿足線程安全。可如果多個線程同時寫入volatile變量時,則需要引入同步語義才能保證線程安全。

模擬10個線程同時寫入volatile變量,一個線程讀counter,執行完后正確結果應該是counter=10。

    public static class WriterTask implements Runnable {
        private final ShareObject share;
        private final CountDownLatch countDownLatch;
        public WriterTask(ShareObject share, CountDownLatch countDownLatch) {
            this.share = share;
            this.countDownLatch = countDownLatch;
        }
        @Override
        public void run() {
            countDownLatch.countDown();
            share.increase();
        }
    }
    
    public class ShareObject {
        private volatile int counter;
        public void increase() {
            this.counter++;
        }
    }

執行結果出現counter=5或6 錯誤結果。

通過 synchronized,Lock或AtomicInteger 原子變量保證了結果的正確。

完整demo https://gist.github.com/onlythinking/ba7ca7aa5faf00a58f4cedae474fa6f6

volatile性能

volatile變量帶來可見性的保證,訪問volatile變量還防止了指令重排序。不過這一切是以犧牲優化(消除緩存,直接操作主存開銷增加)為代價,所以不應該濫用volatile,僅在確實需要增強變量可見性的時候使用。

總結

本文記錄了volatile變量通過消除緩存,防止指令重排序來保證線程可見性,並且在多線程寫入的變量的場景下,不保證線程安全。

歡迎大家留言交流,一起學習分享!!!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※教你寫出一流的銷售文案?

分類
發燒車訊

併發編程 —— 線程池

概述

在程序中,我們會用各種池化技術來緩存創建昂貴的對象,比如線程池、連接池、內存池。一般是預先創建一些對象放入池中,使用的時候直接取出使用,用完歸還以便復用,還會通過一定的策略調整池中緩存對象的數量,實現池的動態伸縮。

由於線程的創建比較昂貴,隨意、沒有控制地創建大量線程會造成性能問題,因此短平快的任務一般考慮使用線程池來處理,而不是直接創建線程。

那麼,如何正確的創建並正確的使用線程池呢,這篇文章就來細看下。

線程池

雖然在 Java 語言中創建線程看上去就像創建一個對象一樣簡單,只需要 new Thread() 就可以了,但實際上創建線程遠不是創建一個對象那麼簡單。

創建對象,僅僅是在 JVM 的堆里分配一塊內存而已;而創建一個線程,卻需要調用操作系統內核的 API,然後操作系統要為線程分配一系列的資源,這個成本就很高了。所以線程是一個重量級的對象,應該避免頻繁創建和銷毀,一般就是採用線程池來避免頻繁的創建和銷毀線程。

 

線程池原理

Java 通過用戶線程與內核線程結合的 1:1 線程模型來實現,Java 將線程的調度和管理設置在了用戶態。在 HotSpot VM 的線程模型中,Java 線程被一對一映射為內核線程。Java 在使用線程執行程序時,需要創建一個內核線程;當該 Java 線程被終止時,這個內核線程也會被回收。因此 Java 線程的創建與銷毀將會消耗一定的計算機資源,從而增加系統的性能開銷。

除此之外,大量創建線程同樣會給系統帶來性能問題,因為內存和 CPU 資源都將被線程搶佔,如果處理不當,就會發生內存溢出、CPU 使用率超負荷等問題。

為了解決上述兩類問題,Java 提供了線程池概念,對於頻繁創建線程的業務場景,線程池可以創建固定的線程數量,並且在操作系統底層,輕量級進程將會把這些線程映射到內核。

線程池可以提高線程復用,又可以固定最大線程使用量,防止無限制地創建線程。當程序提交一個任務需要一個線程時,會去線程池中查找是否有空閑的線程,若有,則直接使用線程池中的線程工作,若沒有,會去判斷當前已創建的線程數量是否超過最大線程數量,如未超過,則創建新線程,如已超過,則進行排隊等待或者直接拋出異常。

 

線程池是一種生產者 – 消費者模式

線程池的設計,普遍採用的都是生產者 – 消費者模式。線程池的使用方是生產者,線程池本身是消費者。

原理實現大致如下:

 1 package com.lyyzoo.test.concurrent.executor;  2 
 3 import java.util.ArrayList;  4 import java.util.List;  5 import java.util.concurrent.BlockingQueue;  6 import java.util.concurrent.LinkedBlockingQueue;  7 
 8 /**
 9  * @author bojiangzhou 2020/02/12 10  */
11 public class CustomThreadPool { 12 
13     public static void main(String[] args) { 14         // 使用有界阻塞隊列 創建線程池
15         CustomThreadPool pool = new CustomThreadPool(2, new LinkedBlockingQueue<>(10)); 16         pool.execute(() -> { 17             System.out.println("提交了一個任務"); 18  }); 19  } 20 
21     // 利用阻塞隊列實現生產者-消費者模式
22     final BlockingQueue<Runnable> workQueue; 23     // 保存內部工作線程
24     final List<Thread> threads = new ArrayList<>(); 25 
26     public CustomThreadPool(int coreSize, BlockingQueue<Runnable> workQueue) { 27         this.workQueue = workQueue; 28         // 創建工作線程
29         for (int i = 0; i < coreSize; i++) { 30             WorkerThread work = new WorkerThread(); 31  work.start(); 32  threads.add(work); 33  } 34  } 35 
36     // 生產者 提交任務
37     public void execute(Runnable command) { 38         try { 39             // 隊列已滿,put 會一直等待
40  workQueue.put(command); 41         } catch (InterruptedException e) { 42  e.printStackTrace(); 43  } 44  } 45 
46     /**
47  * 工作線程負責消費任務,並執行任務 48      */
49     class WorkerThread extends Thread { 50  @Override 51         public void run() { 52             // 循環取任務並執行,take 取不到任務會一直等待
53             while (true) { 54                 try { 55                     Runnable runnable = workQueue.take(); 56  runnable.run(); 57                 } catch (InterruptedException e) { 58  e.printStackTrace(); 59  } 60  } 61  } 62  } 63 }

ThreadPoolExecutor

線程池參數說明

Java 提供的線程池相關的工具類中,最核心的是 ThreadPoolExecutor,通過名字也能看出來,它強調的是 Executor,而不是一般意義上的池化資源。

ThreadPoolExecutor 的構造函數非常複雜,這個最完備的構造函數有 7 個參數:

 

各個參數的含義如下:

  • corePoolSize:表示線程池保有的最小線程數。
  • maximumPoolSize:表示線程池創建的最大線程數。
  • keepAliveTime & unit:如果一個線程空閑了 keepAliveTime & unit 這麼久,而且線程池的線程數大於 corePoolSize ,那麼這個空閑的線程就要被回收了。
  • workQueue:工作隊列,一般定義有界阻塞隊列。
  • threadFactory:通過這個參數你可以自定義如何創建線程,例如你可以給線程指定一個有意義的名字。
  • handler:通過這個參數可以自定義任務的拒絕策略。如果線程池中所有的線程都在忙碌,並且工作隊列也滿了(前提是工作隊列是有界隊列),那麼此時提交任務,線程池就會拒絕接收。ThreadPoolExecutor 已經提供了以下 4 種拒絕策略。
    •   CallerRunsPolicy:提交任務的線程自己去執行該任務。
    •   AbortPolicy:默認的拒絕策略,會 throws RejectedExecutionException。
    •   DiscardPolicy:直接丟棄任務,沒有任何異常拋出。
    •   DiscardOldestPolicy:丟棄最老的任務,其實就是把最早進入工作隊列的任務丟棄,然後把新任務加入到工作隊列。

 

ThreadPoolExecutor 構造完成后,還可以通過如下方法定製默認行為:

  • executor.allowCoreThreadTimeOut(true):將包括“核心線程”在內的,沒有任務分配的所有線程,在等待 keepAliveTime 時間后回收掉。
  • executor.prestartAllCoreThreads():創建線程池后,立即創建核心數個工作線程;線程池默認是在任務來時才創建工作線程。

 

創建線程池示例:

 1 public void test() throws InterruptedException {  2     ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(  3             // 核心線程數
 4             2,  5             // 最大線程數
 6             16,  7             // 線程空閑時間
 8             60, TimeUnit.SECONDS,  9             // 使用有界阻塞隊列
10             new LinkedBlockingQueue<>(1024), 11             // 定義線程創建方式,可自定線程名稱
12             new ThreadFactoryBuilder().setNameFormat("executor-%d").build(), 13             // 自定義拒絕策略,一般和降級策略配合使用
14             (r, executor) -> { 15                 // 隊列已滿,拒絕執行
16                 throw new RejectedExecutionException("Task " + r.toString() +
17                         " rejected from " + executor.toString()); 18  } 19  ); 20 
21     poolExecutor.submit(() -> { 22         LOGGER.info("submit task"); 23  }); 24 }

 

線程池的線程分配流程

任務提交后的大致流程如下圖所示。提交任務后,如果線程數小於 corePoolSize,則創建新線程執行任務,無論當前線程池的線程是否空閑都會創建新的線程。

當創建的線程數等於 corePoolSize 時,提交的任務會被加入到設置的阻塞隊列中。

當隊列滿了,則會創建非核心線程執行任務,直到線程池中的線程數量等於 maximumPoolSize。

當線程數量已經等於 maximumPoolSize 時, 新提交的任務無法加入到等待隊列,也無法創建非核心線程直接執行,如果沒有為線程池設置拒絕策略,這時線程池就會拋出 RejectedExecutionException 異常,即默認拒絕接受任務。

 

線程池默認的拒絕策略就是丟棄任務,所以我們在設置有界隊列時,需要考慮設置合理的拒絕策略,要考慮到高峰時期任務的數量,避免任務被丟棄而影響業務流程。

 

強烈建議使用有界隊列

創建 ThreadPoolExecutor 時強烈建議使用有界隊列。如果設置為無界隊列,那麼一般最大線程數的設置是不起作用的,而且遇到任務高峰時,如果一直往隊列添加任務,容易出現OOM,拋出如下異常。

Exception in thread "http-nio-45678-ClientPoller" java.lang.OutOfMemoryError: GC overhead limit exceeded

 

使用有界隊列時,需要注意,當任務過多時,線程池會觸發執行拒絕策略,線程池默認的拒絕策略會拋出 RejectedExecutionException,這是個運行時異常,對於運行時異常編譯器並不強制 catch 它,所以開發人員很容易忽略,因此默認拒絕策略要慎重使用。如果線程池處理的任務非常重要,建議自定義自己的拒絕策略;並且在實際工作中,自定義的拒絕策略往往和降級策略配合使用。

 

監控線程池的狀態

建議用一些監控手段來觀察線程池的狀態。線程池這個組件往往會表現得任勞任怨、默默無聞,除非是出現了拒絕策略,否則壓力再大都不會拋出一個異常。如果我們能提前觀察到線程池隊列的積壓,或者線程數量的快速膨脹,往往可以提早發現並解決問題。

 1 public static void displayThreadPoolStatus(ThreadPoolExecutor threadPool, String threadPoolName, long period, TimeUnit unit) {
 2     Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
 3         LOGGER.info("[>>ExecutorStatus<<] ThreadPool Name: [{}], Pool Status: [shutdown={}, Terminated={}], Pool Thread Size: {}, Active Thread Count: {}, Task Count: {}, Tasks Completed: {}, Tasks in Queue: {}",
 4                 threadPoolName,
 5                 threadPool.isShutdown(), threadPool.isTerminated(), // 線程是否被終止
 6                 threadPool.getPoolSize(), // 線程池線程數量
 7                 threadPool.getActiveCount(), // 工作線程數
 8                 threadPool.getTaskCount(), // 總任務數
 9                 threadPool.getCompletedTaskCount(), // 已完成的任務數
10                 threadPool.getQueue().size()); // 線程池中線程的數量
11     }, 0, period, unit);
12 }

線程池任務提交方式

提交任務可以通過 execute 和 submit 方法提交任務,下面就來看下它們的區別。

submit 方法簽名:

execute 方法簽名:

 

使用 execute 提交任務

使用 execute 提交任務,線程池內拋出異常會導致線程退出,線程池只能重新創建一個線程。如果每個異步任務都以異常結束,那麼線程池可能完全起不到線程重用的作用。

而且主線程無法捕獲(catch)到線程池內拋出的異常。因為沒有手動捕獲異常進行處理,ThreadGroup 幫我們進行了未捕獲異常的默認處理,向標準錯誤輸出打印了出現異常的線程名稱和異常信息。顯然,這種沒有以統一的錯誤日誌格式記錄錯誤信息打印出來的形式,對生產級代碼是不合適的。

 

如下,execute 提交任務,拋出異常后,從線程名稱可以看出,老線程退出,創建了新的線程。

ThreadGroup 處理未捕獲異常:直接輸出到 System.err

 

解決方式:

  • 以 execute 方法提交到線程池的異步任務,最好在任務內部做好異常處理;
  • 設置自定義的異常處理程序作為保底,比如在聲明線程池時自定義線程池的未捕獲異常處理程序。或者設置全局的默認未捕獲異常處理程序。
 1 // 自定義線程池的未捕獲異常處理程序
 2 ThreadPoolExecutor executor = new ThreadPoolExecutor(8, 8,  3         30, TimeUnit.MINUTES,  4         new LinkedBlockingQueue<>(),  5         new ThreadFactoryBuilder()  6                 .setNameFormat("pool-%d")  7                 .setUncaughtExceptionHandler((Thread t, Throwable e) -> {  8                     log.error("pool happen exception, thread is {}", t, e);  9  }) 10  .build()); 11                 
12 // 設置全局的默認未捕獲異常處理程序
13 static { 14     Thread.setDefaultUncaughtExceptionHandler((thread, throwable)-> { 15         log.error("Thread {} got exception", thread, throwable) 16  }); 17 }  

定義的異常處理程序將未捕獲的異常信息打印到標準日誌中了,老線程同樣會退出。如果要避免這個問題,就需要使用 submit 方法提交任務。

 

使用 submit 提交任務

使用 submit,線程不會退出,但是異常不會記錄,會被生吞掉。查看 FutureTask 源碼可以發現,在執行任務出現異常之後,異常存到了一個 outcome 字段中,只有在調用 get 方法獲取 FutureTask 結果的時候,才會以 ExecutionException 的形式重新拋出異常。所以我們可以通過捕獲 get 方法拋出的異常來判斷線程的任務是否拋出了異常。

 

submit 提交任務,可以通過 Future 獲取返回結果,如果拋出異常,可以捕獲 ExecutionException 得到異常棧信息。通過線程名稱可以看出,老線程也沒有退出。

需要注意的是,使用 submit 時,setUncaughtExceptionHandler 設置的異常處理器不會生效。

 

submit 與 execute 的區別

execute提交的是Runnable類型的任務,而submit提交的是Callable或者Runnable類型的任務;

execute的提交沒有返回值,而submit的提交會返回一個Future類型的對象;

execute提交的時候,如果有異常,就會直接拋出異常,而submit在遇到異常的時候,通常不會立馬拋出異常,而是會將異常暫時存儲起來,等待你調用Future.get()方法的時候,才會拋出異常;

execute 提交的任務拋出異常,老線程會退出,線程池會立即創建一個新的線程。submit 提交的任務拋出異常,老線程不會退出;

線程池設置的 UncaughtExceptionHandler 對 execute 提交的任務生效,對 submit 提交的任務不生效。

線程數設置多少合適

創建多少線程合適,要看多線程具體的應用場景。我們的程序一般都是 CPU 計算和 I/O 操作交叉執行的,由於 I/O 設備的速度相對於 CPU 來說都很慢,所以大部分情況下,I/O 操作執行的時間相對於 CPU 計算來說都非常長,這種場景我們一般都稱為 I/O 密集型計算;和 I/O 密集型計算相對的就是 CPU 密集型計算了,CPU 密集型計算大部分場景下都是純 CPU 計算。I/O 密集型程序和 CPU 密集型程序,計算最佳線程數的方法是不同的。

 

CPU 密集型計算

多線程本質上是提升多核 CPU 的利用率,所以對於一個 4 核的 CPU,每個核一個線程,理論上創建 4 個線程就可以了,再多創建線程也只是增加線程切換的成本。所以,對於 CPU 密集型的計算場景,理論上“線程的數量 = CPU 核數”就是最合適的。不過在工程上,線程的數量一般會設置為“CPU 核數 +1”,這樣的話,當線程因為偶爾的內存頁失效或其他原因導致阻塞時,這個額外的線程可以頂上,從而保證 CPU 的利用率。

 

I/O 密集型的計算場景

如果 CPU 計算和 I/O 操作的耗時是 1:1,那麼 2 個線程是最合適的。如果 CPU 計算和 I/O 操作的耗時是 1:2,那設置 3 個線程是合適的,如下圖所示:CPU 在 A、B、C 三個線程之間切換,對於線程 A,當 CPU 從 B、C 切換回來時,線程 A 正好執行完 I/O 操作。這樣 CPU 和 I/O 設備的利用率都達到了 100%。

會發現,對於 I/O 密集型計算場景,最佳的線程數是與程序中 CPU 計算和 I/O 操作的耗時比相關的,可以總結出這樣一個公式:最佳線程數 =1 +(I/O 耗時 / CPU 耗時)

對於多核 CPU,需要等比擴大,計算公式如下:最佳線程數 =CPU 核數 * [ 1 +(I/O 耗時 / CPU 耗時)]

 

線程池線程數設置 

可通過如下方式獲取CPU核數:

1 /**
2  * 獲取返回CPU核數 3  * 4  * @return 返回CPU核數,默認為8 5  */
6 public static int getCpuProcessors() { 7     return Runtime.getRuntime() != null && Runtime.getRuntime().availableProcessors() > 0 ?
8             Runtime.getRuntime().availableProcessors() : 8; 9 }

 

在一些非核心業務中,我們可以將核心線程數設置小一些,最大線程數量設置為CPU核心數量,阻塞隊列大小根據具體場景設置;不要過大,防止大量任務進入等待隊列而超時,應儘快創建非核心線程執行任務;也不要過小,避免隊列滿了任務被拒絕丟棄。

 1 public ThreadPoolExecutor executor() {  2     int coreSize = getCpuProcessors();  3     ThreadPoolExecutor executor = new ThreadPoolExecutor(  4             2, coreSize,  5             10, TimeUnit.MINUTES,  6             new LinkedBlockingQueue<>(512),  7             new ThreadFactoryBuilder().setNameFormat("executor-%d").build(), 10             new ThreadPoolExecutor.AbortPolicy() 11  );14 
15     return executor; 16 }

 

在一些核心業務中,核心線程數設置為CPU核心數,最大線程數可根據公式 最佳線程數 =CPU 核數 * [ 1 +(I/O 耗時 / CPU 耗時)] 來計算。阻塞隊列可以根據具體業務場景設置,如果線程處理業務非常迅速,我們可以考慮將阻塞隊列設置大一些,處理的請求吞吐量會大些;如果線程處理業務非常耗時,阻塞隊列設置小些,防止請求在阻塞隊列中等待過長時間而導致請求已超時。

public ThreadPoolExecutor executor() { int coreSize = getCpuProcessors(); ThreadPoolExecutor executor = new ThreadPoolExecutor( coreSize, coreSize * 8, 30, TimeUnit.MINUTES, new LinkedBlockingQueue<>(1024), new ThreadFactoryBuilder().setNameFormat("executor-%d").build(), new ThreadPoolExecutor.AbortPolicy() );return executor; }

 

注意:一般不要將 corePoolSize 設置為 0,例如下面的線程池,使用了無界隊列,雖 maximumPoolSize > 0,但實際上只會有一個工作線程,因為其它任務都加入等待隊列了。

1 ThreadPoolExecutor executor = new ThreadPoolExecutor(0, 5, 30, TimeUnit.SECONDS, 3         new LinkedBlockingQueue<>(), 4         new ThreadFactoryBuilder().setNameFormat("test-%d").build() 5 );

 

線程池如何優先啟用非核心線程

如果想讓線程池激進一點,優先開啟更多的線程,而把隊列當成一個後備方案,可以自定義隊列,重寫 offer 方法,因為線程池是通過 offer 方法將任務放入隊列。

 

通過重寫隊列的 offer 方法,直接返回 false,造成這個隊列已滿的假象,線程池在工作隊列滿了無法入隊的情況下會擴容線程池。直到線程數達到最大線程數,就會觸發拒絕策略,此時再通過自定義的拒絕策略將任務通過隊列的 put 方法放入隊列中。這樣就可以優先開啟更多線程,而不是進入隊列了。

 1 public static void main(String[] args) {  2     // ThreadPoolExecutor 通過 offer 將元素放入隊列,重載隊列的 offer 方法,直接返回 false,造成隊列已滿的假象  3     // 隊列滿時,會創建新的線程直到達到 maximumPoolSize,之後會觸發執行拒絕策略
 4     LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>() {  5         private static final long serialVersionUID = 8303142475890427046L;  6 
 7  @Override  8         public boolean offer(Runnable e) {  9             return false; 10  } 11  }; 12 
13     // 當線程達到 maximumPoolSize 時會觸發拒絕策略,此時將任務 put 到隊列中
14     RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() { 15  @Override 16         public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { 17             try { 18                 // 任務拒絕時,通過 put 放入隊列
19  queue.put(r); 20             } catch (InterruptedException e) { 21  Thread.currentThread().interrupt(); 22  } 23  } 24  }; 25 
26     // 構造線程池
27     ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 4, 28             600, TimeUnit.SECONDS, 29  queue, 30             new ThreadFactoryBuilder().setNameFormat("demo-%d").build(), 31  rejectedExecutionHandler); 32 
33     IntStream.rangeClosed(1, 50).forEach(i -> { 34         executor.submit(() -> { 35             log.info("start..."); 36             sleep(9000); 37  }); 38  }); 39 }

優雅的終止線程和線程池

優雅地終止線程

在程序中,我們不能隨便中斷一個線程,因為這是極其不安全的操作,我們無法知道這個線程正運行在什麼狀態,它可能持有某把鎖,強行中斷可能導致鎖不能釋放的問題;或者線程可能在操作數據庫,強行中斷導致數據不一致混亂的問題。正因此,JAVA里將Thread的stop方法設置為過時,以禁止大家使用。

優雅地終止線程,不是自己終止自己,而是在一個線程 T1 中,終止線程 T2;這裏所謂的“優雅”,指的是給 T2 一個機會料理後事,而不是被一劍封喉。兩階段終止模式,就是將終止過程分成兩個階段,其中第一個階段主要是線程 T1 向線程 T2發送終止指令,而第二階段則是線程 T2響應終止指令。

Java 線程進入終止狀態的前提是線程進入 RUNNABLE 狀態,而實際上線程也可能處在休眠狀態,也就是說,我們要想終止一個線程,首先要把線程的狀態從休眠狀態轉換到 RUNNABLE 狀態。如何做到呢?這個要靠 Java Thread 類提供的 interrupt() 方法,它可以將休眠狀態的線程轉換到 RUNNABLE 狀態。

線程轉換到 RUNNABLE 狀態之後,我們如何再將其終止呢?RUNNABLE 狀態轉換到終止狀態,優雅的方式是讓 Java 線程自己執行完 run() 方法,所以一般我們採用的方法是設置一個標誌位,然後線程會在合適的時機檢查這個標誌位,如果發現符合終止條件,則自動退出 run() 方法。這個過程其實就是第二階段:響應終止指令。終止指令,其實包括兩方面內容:interrupt() 方法和線程終止的標誌位。

如果我們在線程內捕獲中斷異常(如Thread.sleep()拋出了中斷一次)之後,需通過 Thread.currentThread().interrupt() 重新設置線程的中斷狀態,因為 JVM 的異常處理會清除線程的中斷狀態。

 

建議自己設置線程終止標誌位,避免線程內調用第三方類庫的方法未處理線程中斷狀態,如下所示。

 1 public class InterruptDemo {  2 
 3     /**
 4  * 輸出:調用 interrupt() 時,只是設置了線程中斷標識,線程依舊會繼續執行當前方法,執行完之後再退出線程。  5  * do something...  6  * continue do something...  7  * do something...  8  * continue do something...  9  * do something... 10  * 線程被中斷... 11  * continue do something... 12      */
13     public static void main(String[] args) throws InterruptedException { 14         Proxy proxy = new Proxy(); 15  proxy.start(); 16 
17         Thread.sleep(6000); 18  proxy.stop(); 19  } 20 
21     static class Proxy { 22         // 自定義線程終止標誌位
23         private volatile boolean terminated = false; 24 
25         private boolean started = false; 26 
27  Thread t; 28 
29         public synchronized void start() { 30             if (started) { 31                 return; 32  } 33             started = true; 34             terminated = false; 35 
36             t = new Thread(() -> { 37                 while (!terminated) { // 取代 while (true)
38                     System.out.println("do something..."); 39                     try { 40                         Thread.sleep(2000); 41                     } catch (InterruptedException e) { 42                         // 如果其它線程中斷此線程,拋出異常時,需重新設置線程中斷狀態,因為 JVM 的異常處理會清除線程的中斷狀態。
43                         System.out.println("線程被中斷..."); 44  Thread.currentThread().interrupt(); 45  } 46                     System.out.println("continue do something..."); 47  } 48                 started = false; 49  }); 50  t.start(); 51  } 52 
53         public synchronized void stop() { 54             // 設置中斷標誌
55             terminated = true; 56  t.interrupt(); 57  } 58  } 59 
60 }

 

優雅的終止線程池

線程池提供了兩個方法來中斷線程池:shutdown() 和 shutdownNow()。

shutdown():是一種很保守的關閉線程池的方法。線程池執行 shutdown() 后,就會拒絕接收新的任務,但是會等待線程池中正在執行的任務和已經進入阻塞隊列的任務都執行完之後才最終關閉線程池。

shutdownNow():相對激進一些,線程池執行 shutdownNow() 后,會拒絕接收新的任務,同時還會中斷線程池中正在執行的任務,已經進入阻塞隊列的任務也被剝奪了執行的機會,不過這些被剝奪執行機會的任務會作為 shutdownNow() 方法的返回值返回。因為 shutdownNow() 方法會中斷正在執行的線程,所以提交到線程池的任務,如果需要優雅地結束,就需要正確地處理線程中斷。如果提交到線程池的任務不允許取消,那就不能使用 shutdownNow() 方法終止線程池。

 

如果想在jvm關閉的時候進行內存清理、對象銷毀等操作,或者僅僅想起個線程然後這個線程不會退出,可以使用Runtime.addShutdownHook。

這個方法的作用就是在JVM中增加一個關閉的鈎子。當程序正常退出、系統調用 System.exit 方法或者虛擬機被關閉時才會執行系統中已經設置的所有鈎子,當系統執行完這些鈎子后,JVM才會關閉。

利用這個性質,就可以在這個最後執行的線程中把線程池優雅的關閉掉。雖然jvm關閉了,但優雅關閉線程池總是好的,特別是涉及到服務端的 tcp 連接。

 1 /**
 2  * 添加Hook在Jvm關閉時優雅的關閉線程池  3  *  4  * @param threadPool 線程池  5  * @param threadPoolName 線程池名稱  6  */
 7 public static void hookShutdownThreadPool(ExecutorService threadPool, String threadPoolName) {  8     Runtime.getRuntime().addShutdownHook(new Thread(() -> {  9         LOGGER.info("[>>ExecutorShutdown<<] Start to shutdown the thead pool: [{}]", threadPoolName); 10         // 使新任務無法提交
11  threadPool.shutdown(); 12         try { 13             // 等待未完成任務結束
14             if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) { 15                 threadPool.shutdownNow(); // 取消當前執行的任務
16                 LOGGER.warn("[>>ExecutorShutdown<<] Interrupt the worker, which may cause some task inconsistent. Please check the biz logs."); 17 
18                 // 等待任務取消的響應
19                 if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) { 20                     LOGGER.error("[>>ExecutorShutdown<<] Thread pool can't be shutdown even with interrupting worker threads, which may cause some task inconsistent. Please check the biz logs."); 21  } 22  } 23         } catch (InterruptedException ie) { 24             // 重新取消當前線程進行中斷
25  threadPool.shutdownNow(); 26             LOGGER.error("[>>ExecutorShutdown<<] The current server thread is interrupted when it is trying to stop the worker threads. This may leave an inconsistent state. Please check the biz logs."); 27 
28             // 保留中斷狀態
29  Thread.currentThread().interrupt(); 30  } 31 
32         LOGGER.info("[>>ExecutorShutdown<<] Finally shutdown the thead pool: [{}]", threadPoolName); 33  })); 34 }

Executors

考慮到 ThreadPoolExecutor 的構造函數實在是有些複雜,所以 Java 併發包里提供了一個線程池的靜態工廠類 Executors,利用 Executors 你可以快速創建線程池。

但《阿里巴巴 Java 開發手冊》中提到,禁止使用這些方法來創建線程池,而應該手動 new ThreadPoolExecutor 來創建線程池。最重要的原因是:Executors 提供的很多方法默認使用的都是無界的 LinkedBlockingQueue,高負載情境下,無界隊列很容易導致 OOM,而 OOM 會導致所有請求都無法處理,這是致命問題。最典型的就是 newFixedThreadPool 和 newCachedThreadPool,可能因為資源耗盡導致 OOM 問題。

 

newCachedThreadPool

具有緩存性質的線程池,線程最大空閑時間60s,線程可重複利用,沒有最大線程數限制。使用的是 SynchronousQueue 無容量阻塞隊列,沒有最大線程數限制。這意味着,只要有請求到來,就必須找到一條工作線程來處理,如果當前沒有空閑的線程就再創建一條新的。

高併發情況下,大量的任務進來後會創建大量的線程,導致OOM(無法創建本地線程):

1 [11:30:30.487] [http-nio-45678-exec-1] [ERROR] [.a.c.c.C.[.[.[/].[dispatcherServlet]:175 ] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; 2     nested exception is java.lang.OutOfMemoryError: unable to create new native thread] with root cause 3 java.lang.OutOfMemoryError: unable to create new native thread 

 

newFixedThreadPool

具有固定數量的線程池,核心線程數等於最大線程數,超出最大線程數進行等待。使用的是 LinkedBlockingQueue 無界阻塞隊列。雖然使用 newFixedThreadPool 可以把工作線程控制在固定的數量上,但任務隊列是無界的。如果任務較多並且執行較慢的話,隊列可能會快速積壓,撐爆內存導致 OOM。

如果一直往這個無界隊列中添加任務,不久就會出現OOM異常(內存佔滿):

1 Exception in thread "http-nio-45678-ClientPoller" 
2     java.lang.OutOfMemoryError: GC overhead limit exceeded

 

newSingleThreadExecutor

核心線程數與最大線程數均為1,可用於當鎖控制同步。使用的是 LinkedBlockingQueue 無界阻塞隊列。

 

newScheduledThreadPool

具有時間調度性的線程池,必須初始化核心線程數。

沒有最大線程數限制,線程最大空閑時間為0,空閑線程執行完即銷毀。底層使用 DelayedWorkQueue 實現延遲特性。

線程池創建正確姿勢

最後,總結一下,從如下的一些方面考慮如何正確地創建線程池。

線程池配置

我們需要根據自己的場景、併發情況來評估線程池的幾個核心參數,包括核心線程數、最大線程數、線程回收策略、工作隊列的類型,以及拒絕策略,確保線程池的工作行為符合需求,一般都需要設置有界的工作隊列和可控的線程數。

要根據任務的“輕重緩急”來指定線程池的核心參數,包括線程數、回收策略和任務隊列:

  • 對於執行比較慢、數量不大的 IO 任務,要考慮更多的線程數,而不需要太大的隊列。
  • 對於吞吐量較大的計算型任務,線程數量不宜過多,可以是 CPU 核數或核數 *2(理由是,線程一定調度到某個 CPU 進行執行,如果任務本身是 CPU 綁定的任務,那麼過多的線程只會增加線程切換的開銷,並不能提升吞吐量),但可能需要較長的隊列來做緩衝。

 

任何時候,都應該為自定義線程池指定有意義的名稱,以方便排查問題。當出現線程數量暴增、線程死鎖、線程佔用大量 CPU、線程執行出現異常等問題時,我們往往會抓取線程棧。此時,有意義的線程名稱,就可以方便我們定位問題。

除了建議手動聲明線程池以外,還建議用一些監控手段來觀察線程池的狀態。如果我們能提前觀察到線程池隊列的積壓,或者線程數量的快速膨脹,往往可以提早發現並解決問題。

 

確認線程池本身是不是復用的

既然使用了線程池就需要確保線程池是在復用的,每次 new 一個線程池出來可能比不用線程池還糟糕。如果你沒有直接聲明線程池而是使用其他同學提供的類庫來獲得一個線程池,請務必查看源碼,以確認線程池的實例化方式和配置是符合預期的。

 

斟酌線程池的混用策略

不要盲目復用線程池,別人定義的線程池屬性不一定適合你的任務,而且混用會相互干擾。

另外,Java 8 的 parallel stream 背後是共享同一個 ForkJoinPool,默認并行度是 CPU 核數 -1。對於 CPU 綁定的任務來說,使用這樣的配置比較合適,但如果集合操作涉及同步 IO 操作的話(比如數據庫操作、外部服務調用等),建議自定義一個 ForkJoinPool(或普通線程池)。因此在使用 Java8 的并行流時,建議只用在計算密集型的任務,IO密集型的任務建議自定義線程池來提交任務,避免影響其它業務。

 

CommonExecutor

如下是我自己封裝的一個線程池工具類,還提供了執行批量任務的方法,關於批量任務後面再單獨寫篇文章來介紹。

 1 package org.hzero.core.util;  2 
 3 import java.util.ArrayList;  4 import java.util.Collections;  5 import java.util.List;  6 import java.util.concurrent.*;  7 import java.util.stream.Collectors;  8 import javax.annotation.Nonnull;  9 
 10 import com.google.common.util.concurrent.ThreadFactoryBuilder;  11 import org.apache.commons.collections4.CollectionUtils;  12 import org.apache.commons.lang3.RandomUtils;  13 import org.slf4j.Logger;  14 import org.slf4j.LoggerFactory;  15 import org.springframework.dao.DuplicateKeyException;  16 
 17 import io.choerodon.core.exception.CommonException;  18 
 19 import org.hzero.core.base.BaseConstants;  20 
 21 /**
 22  * @author bojiangzhou 2020/02/24  23  */
 24 public class CommonExecutor {  25 
 26     private static final Logger LOGGER = LoggerFactory.getLogger(CommonExecutor.class);  27 
 28     private static final ThreadPoolExecutor BASE_EXECUTOR;  29 
 30     static {  31         BASE_EXECUTOR = buildThreadFirstExecutor("BaseExecutor");  32  }  33 
 34     /**
 35  * 構建線程優先的線程池  36  * <p>  37  * 線程池默認是當核心線程數滿了后,將任務添加到工作隊列中,當工作隊列滿了之後,再創建線程直到達到最大線程數。  38  *  39  * <p>  40  * 線程優先的線程池,就是在核心線程滿了之後,繼續創建線程,直到達到最大線程數之後,再把任務添加到工作隊列中。  41  *  42  * <p>  43  * 此方法默認設置核心線程數為 CPU 核數,最大線程數為 8倍 CPU 核數,空閑線程超過 5 分鐘銷毀,工作隊列大小為 65536。  44  *  45  * @param poolName 線程池名稱  46  * @return ThreadPoolExecutor  47      */
 48     public static ThreadPoolExecutor buildThreadFirstExecutor(String poolName) {  49         int coreSize = CommonExecutor.getCpuProcessors();  50         int maxSize = coreSize * 8;  51         return buildThreadFirstExecutor(coreSize, maxSize, 5, TimeUnit.MINUTES, 1 << 16, poolName);  52  }  53 
 54     /**
 55  * 構建線程優先的線程池  56  * <p>  57  * 線程池默認是當核心線程數滿了后,將任務添加到工作隊列中,當工作隊列滿了之後,再創建線程直到達到最大線程數。  58  *  59  * <p>  60  * 線程優先的線程池,就是在核心線程滿了之後,繼續創建線程,直到達到最大線程數之後,再把任務添加到工作隊列中。  61  *  62  * @param corePoolSize 核心線程數  63  * @param maximumPoolSize 最大線程數  64  * @param keepAliveTime 空閑線程的空閑時間  65  * @param unit 時間單位  66  * @param workQueueSize 工作隊列容量大小  67  * @param poolName 線程池名稱  68  * @return ThreadPoolExecutor  69      */
 70     public static ThreadPoolExecutor buildThreadFirstExecutor(int corePoolSize,  71                                                               int maximumPoolSize,  72                                                               long keepAliveTime,  73  TimeUnit unit,  74                                                               int workQueueSize,  75  String poolName) {  76         // 自定義隊列,優先開啟更多線程,而不是放入隊列
 77         LinkedBlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>(workQueueSize) {  78             private static final long serialVersionUID = 5075561696269543041L;  79 
 80  @Override  81             public boolean offer(@Nonnull Runnable o) {  82                 return false; // 造成隊列已滿的假象
 83  }  84  };  85 
 86         // 當線程達到 maximumPoolSize 時會觸發拒絕策略,此時將任務 put 到隊列中
 87         RejectedExecutionHandler rejectedExecutionHandler = (runnable, executor) -> {  88             try {  89                 // 任務拒絕時,通過 offer 放入隊列
 90  queue.put(runnable);  91             } catch (InterruptedException e) {  92                 LOGGER.warn("{} Queue offer interrupted. ", poolName, e);  93  Thread.currentThread().interrupt();  94  }  95  };  96 
 97         ThreadPoolExecutor executor = new ThreadPoolExecutor(  98  corePoolSize, maximumPoolSize,  99  keepAliveTime, unit, 100  queue, 101                 new ThreadFactoryBuilder() 102                         .setNameFormat(poolName + "-%d") 103                         .setUncaughtExceptionHandler((Thread thread, Throwable throwable) -> { 104                             LOGGER.error("{} catching the uncaught exception, ThreadName: [{}]", poolName, thread.toString(), throwable); 105  }) 106  .build(), 107  rejectedExecutionHandler 108  ); 109 
110  CommonExecutor.displayThreadPoolStatus(executor, poolName); 111  CommonExecutor.hookShutdownThreadPool(executor, poolName); 112         return executor; 113  } 114 
115     /**
116  * 批量提交異步任務,使用默認的線程池
117 * 118 * @param tasks 將任務轉化為 AsyncTask 批量提交 119 */ 120 public static <T> List<T> batchExecuteAsync(List<AsyncTask<T>> tasks, @Nonnull String taskName) { 121 return batchExecuteAsync(tasks, BASE_EXECUTOR, taskName); 122 } 123 124 /** 125 * 批量提交異步任務,執行失敗可拋出異常或返回異常編碼即可 <br> 126 * <p> 127 * 需注意提交的異步任務無法控制事務,一般需容忍產生一些垃圾數據的情況下才能使用異步任務,異步任務執行失敗將拋出異常,主線程可回滾事務. 128 * <p> 129 * 異步任務失敗后,將取消剩餘的任務執行. 130 * 131 * @param tasks 將任務轉化為 AsyncTask 批量提交 132 * @param executor 線程池,需自行根據業務場景創建相應的線程池 133 * @return 返回執行結果 134 */ 135 public static <T> List<T> batchExecuteAsync(@Nonnull List<AsyncTask<T>> tasks, @Nonnull ThreadPoolExecutor executor, @Nonnull String taskName) { 136 if (CollectionUtils.isEmpty(tasks)) { 137 return Collections.emptyList(); 138 } 139 140 int size = tasks.size(); 141 142 List<Callable<T>> callables = tasks.stream().map(t -> (Callable<T>) () -> { 143 try { 144 T r = t.doExecute(); 145 146 LOGGER.debug("[>>Executor<<] Async task execute success. ThreadName: [{}], BatchTaskName: [{}], SubTaskName: [{}]", 147 Thread.currentThread().getName(), taskName, t.taskName()); 148 return r; 149 } catch (Throwable e) { 150 LOGGER.warn("[>>Executor<<] Async task execute error. ThreadName: [{}], BatchTaskName: [{}], SubTaskName: [{}], exception: {}", 151 Thread.currentThread().getName(), taskName, t.taskName(), e.getMessage()); 152 throw e; 153 } 154 }).collect(Collectors.toList()); 155 156 CompletionService<T> cs = new ExecutorCompletionService<>(executor, new LinkedBlockingQueue<>(size)); 157 List<Future<T>> futures = new ArrayList<>(size); 158 LOGGER.info("[>>Executor<<] Start async tasks, BatchTaskName: [{}], TaskSize: [{}]", taskName, size); 159 160 for (Callable<T> task : callables) { 161 futures.add(cs.submit(task)); 162 } 163 164 List<T> resultList = new ArrayList<>(size); 165 for (int i = 0; i < size; i++) { 166 try { 167 Future<T> future = cs.poll(6, TimeUnit.MINUTES); 168 if (future != null) { 169 T result = future.get(); 170 resultList.add(result); 171 LOGGER.debug("[>>Executor<<] Async task [{}] - [{}] execute success, result: {}", taskName, i, result); 172 } else { 173 cancelTask(futures); 174 LOGGER.error("[>>Executor<<] Async task [{}] - [{}] execute timeout, then cancel other tasks.", taskName, i); 175 throw new CommonException(BaseConstants.ErrorCode.TIMEOUT); 176 } 177 } catch (ExecutionException e) { 178 LOGGER.warn("[>>Executor<<] Async task [{}] - [{}] execute error, then cancel other tasks.", taskName, i, e); 179 cancelTask(futures); 180 Throwable throwable = e.getCause(); 181 if (throwable instanceof CommonException) { 182 throw (CommonException) throwable; 183 } else if (throwable instanceof DuplicateKeyException) { 184 throw (DuplicateKeyException) throwable; 185 } else { 186 throw new CommonException("error.executorError", e.getCause().getMessage()); 187 } 188 } catch (InterruptedException e) { 189 cancelTask(futures); 190 Thread.currentThread().interrupt(); // 重置中斷標識 191 LOGGER.error("[>>Executor<<] Async task [{}] - [{}] were interrupted.", taskName, i); 192 throw new CommonException(BaseConstants.ErrorCode.ERROR); 193 } 194 } 195 LOGGER.info("[>>Executor<<] Finish async tasks , BatchTaskName: [{}], TaskSize: [{}]", taskName, size); 196 return resultList; 197 } 198 199 /** 200 * 根據一定周期輸出線程池的狀態 201 * 202 * @param threadPool 線程池 203 * @param threadPoolName 線程池名稱 204 */ 205 public static void displayThreadPoolStatus(ThreadPoolExecutor threadPool, String threadPoolName) { 206 displayThreadPoolStatus(threadPool, threadPoolName, RandomUtils.nextInt(60, 600), TimeUnit.SECONDS); 207 } 208 209 /** 210 * 根據一定周期輸出線程池的狀態 211 * 212 * @param threadPool 線程池 213 * @param threadPoolName 線程池名稱 214 * @param period 周期 215 * @param unit 時間單位 216 */ 217 public static void displayThreadPoolStatus(ThreadPoolExecutor threadPool, String threadPoolName, long period, TimeUnit unit) { 218 Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> { 219 LOGGER.info("[>>ExecutorStatus<<] ThreadPool Name: [{}], Pool Status: [shutdown={}, Terminated={}], Pool Thread Size: {}, Active Thread Count: {}, Task Count: {}, Tasks Completed: {}, Tasks in Queue: {}", 220 threadPoolName, 221 threadPool.isShutdown(), threadPool.isTerminated(), // 線程是否被終止 222 threadPool.getPoolSize(), // 線程池線程數量 223 threadPool.getActiveCount(), // 工作線程數 224 threadPool.getTaskCount(), // 總任務數 225 threadPool.getCompletedTaskCount(), // 已完成的任務數 226 threadPool.getQueue().size()); // 線程池中線程的數量 227 }, 0, period, unit); 228 } 229 230 /** 231 * 添加Hook在Jvm關閉時優雅的關閉線程池 232 * 233 * @param threadPool 線程池 234 * @param threadPoolName 線程池名稱 235 */ 236 public static void hookShutdownThreadPool(ExecutorService threadPool, String threadPoolName) { 237 Runtime.getRuntime().addShutdownHook(new Thread(() -> { 238 LOGGER.info("[>>ExecutorShutdown<<] Start to shutdown the thead pool: [{}]", threadPoolName); 239 // 使新任務無法提交 240 threadPool.shutdown(); 241 try { 242 // 等待未完成任務結束 243 if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) { 244 threadPool.shutdownNow(); // 取消當前執行的任務 245 LOGGER.warn("[>>ExecutorShutdown<<] Interrupt the worker, which may cause some task inconsistent. Please check the biz logs."); 246 247 // 等待任務取消的響應 248 if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) { 249 LOGGER.error("[>>ExecutorShutdown<<] Thread pool can't be shutdown even with interrupting worker threads, which may cause some task inconsistent. Please check the biz logs."); 250 } 251 } 252 } catch (InterruptedException ie) { 253 // 重新取消當前線程進行中斷 254 threadPool.shutdownNow(); 255 LOGGER.error("[>>ExecutorShutdown<<] The current server thread is interrupted when it is trying to stop the worker threads. This may leave an inconsistent state. Please check the biz logs."); 256 257 // 保留中斷狀態 258 Thread.currentThread().interrupt(); 259 } 260 261 LOGGER.info("[>>ExecutorShutdown<<] Finally shutdown the thead pool: [{}]", threadPoolName); 262 })); 263 } 264 265 /** 266 * 獲取返回CPU核數 267 * 268 * @return 返回CPU核數,默認為8 269 */ 270 public static int getCpuProcessors() { 271 return Runtime.getRuntime() != null && Runtime.getRuntime().availableProcessors() > 0 ? 272 Runtime.getRuntime().availableProcessors() : 8; 273 } 274 275 private static <T> void cancelTask(List<Future<T>> futures) { 276 for (Future<T> future : futures) { 277 if (!future.isDone()) { 278 future.cancel(true); 279 } 280 } 281 } 282 283 }

AsyncTask:

 1 package org.hzero.core.util;  2 
 3 import java.util.UUID;  4 
 5 public interface AsyncTask<T> {  6 
 7     default String taskName() {  8         return UUID.randomUUID().toString();  9  } 10 
11  T doExecute(); 12 }

 

————————————————————————————————————–

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準