成人午夜视频全免费观看高清-秋霞福利视频一区二区三区-国产精品久久久久电影小说-亚洲不卡区三一区三区一区

Java并發(fā)編程:線程及同步的性能——線程池

Java并發(fā)編程:線程及同步的性能——線程池

成都創(chuàng)新互聯(lián)主營下陸網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營網(wǎng)站建設(shè)方案,成都app軟件開發(fā)公司,下陸h5微信小程序定制開發(fā)搭建,下陸網(wǎng)站營銷推廣歡迎下陸等地區(qū)企業(yè)咨詢

線程池和ThreadPoolExecutors

雖然在程序中可以直接使用Thread類型來進行線程操作,但是更多的情況是使用線程池,尤其是在Java EE應(yīng)用服務(wù)器中,一般會使用若干個線程池來處理來自客戶端的請求。Java中對于線程池的支持,來自ThreadPoolExecutor。一些應(yīng)用服務(wù)器也確實是使用的ThreadPoolExecutor來實現(xiàn)線程池。

對于線程池的性能調(diào)優(yōu),最重要的參數(shù)就是線程池的大小。

對于任何線程池而言,它們的工作方式幾乎都是相同的:

  1. 任務(wù)被投放到一個隊列中(隊列的數(shù)量不定)
  2. 線程從隊列中取得任務(wù)并執(zhí)行
  3. 線程完成任務(wù)后,繼續(xù)嘗試從隊列中取得任務(wù),如果隊列為空,那么線程進入等待狀態(tài)

線程池往往擁有最小和最大線程數(shù):

  1. 最小線程數(shù),即當(dāng)任務(wù)隊列為空時,線程池中最少需要保持的線程數(shù)量,這樣做是考慮到創(chuàng)建線程是一個相對耗費資源的操作,應(yīng)當(dāng)盡可能地避免,當(dāng)有新任務(wù)被投入隊列時,總會有線程能夠立即對它進行處理。
  2. 最大線程數(shù),當(dāng)需要處理的任務(wù)過多時,線程池能夠擁有的最大線程數(shù)。這樣是為了保證不會有過多的線程被創(chuàng)建出來,因為線程的運行需要依賴于CPU資源和其它各種資源,當(dāng)線程過多時,反而會降低性能。

在ThreadPoolExecutor和其相關(guān)的類型中,最小線程數(shù)被稱為線程池核心規(guī)模(Core Pool Size),在其它Java應(yīng)用服務(wù)器的實現(xiàn)中,這個數(shù)量也許被稱為最小線程數(shù)(MinThreads),但是它們的概念是相同的。

但是在對線程池進行規(guī)模變更(Resizing)的時候,ThreadPoolExecutor和其它線程池的實現(xiàn)也許存在的很大的差別。

一個最簡單的情況是:當(dāng)有新任務(wù)需要被執(zhí)行,且當(dāng)前所有的線程都被占用時,ThreadPoolExecutor和其它實現(xiàn)通常都會新創(chuàng)建一個線程來執(zhí)行這個新任務(wù)(直到達到了最大線程數(shù))。

設(shè)置最大線程數(shù)

最合適的最大線程數(shù)該怎么確定,依賴以下兩個方面:

  1. 任務(wù)的特征
  2. 計算機的硬件情況

為了方便討論,下面假設(shè)JVM有4個可用的CPU。那么任務(wù)也很明確,就是要最大程度地“壓榨”它們的資源,千方百計的提高CPU的利用率。

那么,最大線程數(shù)最少需要被設(shè)置成4,因為有4個可用的CPU,意味著最多能夠并行地執(zhí)行4個任務(wù)。當(dāng)然,垃圾回收(Garbage Collection)在這個過程中也會造成一些影響,但是它們往往不需要使用整個CPU。一個例外是,當(dāng)使用了CMS或者G1垃圾回收算法時,需要有足夠的CPU資源進行垃圾回收。

那么是否有必要將線程數(shù)量設(shè)置的更大呢?這就取決于任務(wù)的特征了。

假設(shè)當(dāng)任務(wù)是計算密集型的,意味著任務(wù)不需要執(zhí)行IO操作,例如讀取數(shù)據(jù)庫,讀取文件等,因此它們不涉及到同步的問題,任務(wù)之間完全是獨立的。比如使用一個批處理程序讀取Mock數(shù)據(jù)源的數(shù)據(jù),測試在不線程池擁有不同線程數(shù)量時的性能,得到下表:

Java并發(fā)編程:線程及同步的性能——線程池

從上面中得到一些結(jié)論:

  1. 當(dāng)線程數(shù)為4時,達到最優(yōu)性能,再增加線程數(shù)量時并沒有更好的性能,因為此時CPU的利用率已經(jīng)達到了最高,在增加線程只會增加線程之間爭奪CPU資源的行為,因此反而降低了性能。
  2. 即使在CPU利用率達到最高時,基線百分比也不是理想中的25%,這是因為雖然在程序運行過程中,CPU資源并不是只被應(yīng)用程序線程獨享的,一些后臺線程有時也會需要CPU資源,比如GC線程和系統(tǒng)的一些線程等。

當(dāng)計算是通過Servlet觸發(fā)的時候,性能數(shù)據(jù)是下面這個樣子的(Load Generator會同時發(fā)送20個請求):

Java并發(fā)編程:線程及同步的性能——線程池

從上表中可以得到的結(jié)論:

  1. 在線程數(shù)量為4時,性能最優(yōu)。因為此任務(wù)的類型是計算密集型的,只有4個CPU,因此線程數(shù)量為4時,達到最優(yōu)情況。
  2. 隨著線程數(shù)量逐漸增加,性能下降,因為線程之間會互相爭奪CPU資源,造成頻繁切換線程執(zhí)行上下文環(huán)境,而這些切換只會浪費CPU資源。
  3. 性能下降的速度并不明顯,這也是因為任務(wù)類型是計算密集型的緣故,如果性能瓶頸不是CPU提供的計算資源,而是外部的資源,如數(shù)據(jù)庫,文件操作等,那么增加線程數(shù)量帶來的性能下降也許會更加明顯。

下面,從Client的角度考慮一下問題,并發(fā)Client的數(shù)量對于Server的響應(yīng)時間會有什么影響呢?還是同樣地環(huán)境,當(dāng)并發(fā)Client數(shù)量逐漸增加時,響應(yīng)時間會如下發(fā)生變化:

Java并發(fā)編程:線程及同步的性能——線程池

因為任務(wù)類型是計算密集型的,當(dāng)并發(fā)Client數(shù)量時1,2,4時,平均響應(yīng)時間都是最優(yōu)的,然而當(dāng)出現(xiàn)多余4個Client時,性能會隨著Client的增加發(fā)生顯著地下降。

當(dāng)Client數(shù)量增加時,你也許會想通過增加服務(wù)端線程池的線程數(shù)量來提高性能,可是在CPU密集型任務(wù)的情況下,這么做只會降低性能。因為系統(tǒng)的瓶頸就是CPU資源,冒然增加線程池的線程數(shù)量只會讓對于這種資源的競爭更加激烈。

所以,在面對性能方面的問題時。第一步永遠是了解系統(tǒng)的瓶頸在哪里,這樣才能夠有的放矢。如果冒然進行所謂的“調(diào)優(yōu)”,讓對瓶頸資源的競爭更加激烈,那么帶來的只會是性能的進一步下降。相反,如果讓對瓶頸資源的競爭變的緩和,那么性能通常則會提高。

在上面的場景中,如果從ThreadPoolExecutor的角度進行考慮,那么在任務(wù)隊列中一直會有任務(wù)處于掛起(Pending)的狀態(tài)(因為Client的每個請求對應(yīng)的就是一個任務(wù)),而所有的可用線程都在工作,CPU正在滿負荷運轉(zhuǎn)。這個時候添加線程池的線程數(shù)量,讓這些添加的線程領(lǐng)取一些掛起的任務(wù),會發(fā)生什么事情呢?這時帶來的只會是線程之間對于CPU資源的爭奪更加激烈,降低了性能。

設(shè)置最小線程數(shù)

設(shè)置了最大線程數(shù)之后,還需要設(shè)置最小線程數(shù)。對于絕大部分場景,將它設(shè)置的和最大線程數(shù)相等就可以了。

將最小線程數(shù)設(shè)置的小于最大線程數(shù)的初衷是為了節(jié)省資源,因為每多創(chuàng)建一個線程都會耗費一定量的資源,尤其是線程棧所需要的資源。但是在一個系統(tǒng)中,針對硬件資源以及任務(wù)特點選定了最大線程數(shù)之后,就表示這個系統(tǒng)總是會利用這些線程的,那么還不如在一開始就讓線程池把需要的線程準(zhǔn)備好。然而,把最小線程數(shù)設(shè)置的小于最大線程數(shù)所帶來的影響也是非常小的,一般都不會察覺到有什么不同。

在批處理程序中,最小線程數(shù)是否等于最大線程數(shù)并不重要。因為最后線程總是需要被創(chuàng)建出來的,所以程序的運行時間應(yīng)該幾乎相同。對于服務(wù)器程序而言,影響也不大,但是一般而言,線程池中的線程在“熱身”階段就應(yīng)該被創(chuàng)建出來,所以這也是為什么建議將最小線程數(shù)設(shè)置的等于最大線程數(shù)的原因。

在一些場景中,也需要要設(shè)置一個不同的最小線程數(shù)。比如當(dāng)一個系統(tǒng)最大需要同時處理2000個任務(wù),而平均任務(wù)數(shù)量只是20個情況下,就需要將最小線程數(shù)設(shè)置成20,而不是等于其最大線程數(shù)2000。此時如果還是將最小線程數(shù)設(shè)置的等于最大線程數(shù)的話,那么閑置線程(Idle Thread)占用的資源就比較可觀了,尤其是當(dāng)使用了ThreadLocal類型的變量時。

線程池任務(wù)數(shù)量(Thread Pool Task Sizes)

線程池有一個列表或者隊列的數(shù)據(jù)結(jié)構(gòu)來存放需要被執(zhí)行的任務(wù)。顯然,在某些情況下,任務(wù)數(shù)量的增長速度會大于其被執(zhí)行的速度。如果這個任務(wù)代表的是一個來自Client的請求,那么也就意味著該Client會等待比較長的時間。顯然這是不可接受的,尤其對于提供Web服務(wù)的服務(wù)器程序而言。

所以,線程池會有機制來限制列表/隊列中任務(wù)的數(shù)量。但是,和設(shè)置最大線程數(shù)一樣,并沒有一個放之四海而皆準(zhǔn)的最優(yōu)任務(wù)數(shù)量。這還是要取決于具體的任務(wù)類型和不斷的進行性能測試。

對于ThreadPoolExecutor而言,當(dāng)任務(wù)數(shù)量達到最大時,再嘗試增加新的任務(wù)就會失敗。ThreadPoolExecutor有一個rejectedExecution方法用來拒絕該任務(wù)。這會導(dǎo)致應(yīng)用服務(wù)器返回一個HTTP狀態(tài)碼500,當(dāng)然這種信息最好以更友好的方式傳達給Client,比如解釋一下為什么你的請求被拒絕了。

定制ThreadPoolExecutor

線程池在同時滿足以下三個條件時,就會創(chuàng)建一個新的線程:

  1. 有任務(wù)需要被執(zhí)行
  2. 當(dāng)前線程池中所有的線程都處于工作狀態(tài)
  3. 當(dāng)前線程池的線程數(shù)沒有達到最大線程數(shù)

至于線程池會如何創(chuàng)建這個新的線程,則是根據(jù)任務(wù)隊列的種類:

  1. 任務(wù)隊列是 SynchronousQueue 這個隊列的特點是,它并不能放置任何任務(wù)在其隊列中,當(dāng)有任務(wù)被提交時,使用SynchronousQueue的線程池會立即為該任務(wù)創(chuàng)建一個線程(如果線程數(shù)量沒有達到最大時,如果達到了最大,那么該任務(wù)會被拒絕)。這種隊列適合于當(dāng)任務(wù)數(shù)量較小時采用。也就是說,在使用這種隊列時,未被執(zhí)行的任務(wù)沒有一個容器來暫時儲存。
  2. 任務(wù)隊列是 無限隊列(Unbound Queue) ×××限的隊列可以是諸如LinkedBlockingQueue這種類型,在這種情況下,任何被提交的任務(wù)都不會被拒絕。但是線程池會忽略最大線程數(shù)這一參數(shù),意味著線程池的最大線程數(shù)就變成了設(shè)置的最小線程數(shù)。所以在使用這種隊列時,通常會將最大線程數(shù)設(shè)置的和最小線程數(shù)相等。這就相當(dāng)于使用了一個固定了線程數(shù)量的線程池。
  3. 任務(wù)隊列是 有限隊列(Bounded Queue) 當(dāng)使用的隊列是諸如ArrayBlockingQueue這種有限隊列的時候,來決定什么時候創(chuàng)建新線程的算法就相對復(fù)雜一些了。比如,最小線程數(shù)是4,最大線程數(shù)是8,任務(wù)隊列最多能夠容納10個任務(wù)。在這種情況下,當(dāng)任務(wù)逐漸被添加到隊列中,直到隊列被占滿(10個任務(wù)),此時線程池中的工作線程仍然只有4個,即最小線程數(shù)。只有當(dāng)仍然有任務(wù)希望被放置到隊列中的時候,線程池才會新創(chuàng)建一個線程并從隊列頭部拿走一個任務(wù),以騰出位置來容納這個最新被提交的任務(wù)。

關(guān)于如何定制ThreadPoolExecutor,遵循KISS原則(Keep It Simple, Stupid)就好了。比如將最大線程數(shù)和最小線程數(shù)設(shè)置的相等,然后根據(jù)情況選擇有限隊列或者無限隊列。

總結(jié)

  1. 線程池是對象池的一個有用的例子,它能夠節(jié)省在創(chuàng)建它們時候的資源開銷。并且線程池對系統(tǒng)中的線程數(shù)量也起到了很好的限制作用。

  2. 線程池中的線程數(shù)量必須仔細的設(shè)置,否則冒然增加線程數(shù)量只會帶來性能的下降。

  3. 在定制ThreadPoolExecutor時,遵循KISS原則,通常情況下會提供最好的性能。

ForkJoinPool

在Java 7中引入了一種新的線程池:ForkJoinPool。

它同ThreadPoolExecutor一樣,也實現(xiàn)了Executor和ExecutorService接口。它使用了一個無限隊列來保存需要執(zhí)行的任務(wù),而線程的數(shù)量則是通過構(gòu)造函數(shù)傳入,如果沒有向構(gòu)造函數(shù)中傳入希望的線程數(shù)量,那么當(dāng)前計算機可用的CPU數(shù)量會被設(shè)置為線程數(shù)量作為默認(rèn)值。

ForkJoinPool主要用來使用分治法(Divide-and-Conquer Algorithm)來解決問題。典型的應(yīng)用比如快速排序算法。這里的要點在于,F(xiàn)orkJoinPool需要使用相對少的線程來處理大量的任務(wù)。比如要對1000萬個數(shù)據(jù)進行排序,那么會將這個任務(wù)分割成兩個500萬的排序任務(wù)和一個針對這兩組500萬數(shù)據(jù)的合并任務(wù)。以此類推,對于500萬的數(shù)據(jù)也會做出同樣的分割處理,到最后會設(shè)置一個閾值來規(guī)定當(dāng)數(shù)據(jù)規(guī)模到多少時,停止這樣的分割處理。比如,當(dāng)元素的數(shù)量小于10時,會停止分割,轉(zhuǎn)而使用插入排序?qū)λ鼈冞M行排序。

那么到最后,所有的任務(wù)加起來會有大概2000000+個。問題的關(guān)鍵在于,對于一個任務(wù)而言,只有當(dāng)它所有的子任務(wù)完成之后,它才能夠被執(zhí)行。

所以當(dāng)使用ThreadPoolExecutor時,使用分治法會存在問題,因為ThreadPoolExecutor中的線程無法像任務(wù)隊列中再添加一個任務(wù)并且在等待該任務(wù)完成之后再繼續(xù)執(zhí)行。而使用ForkJoinPool時,就能夠讓其中的線程創(chuàng)建新的任務(wù),并掛起當(dāng)前的任務(wù),此時線程就能夠從隊列中選擇子任務(wù)執(zhí)行。

比如,我們需要統(tǒng)計一個double數(shù)組中小于0.5的元素的個數(shù),那么可以使用ForkJoinPool進行實現(xiàn)如下:

public class ForkJoinTest {
    private double[] d;
    private class ForkJoinTask extends RecursiveTask<Integer> {
        private int first;
        private int last;
        public ForkJoinTask(int first, int last) {
            this.first = first;
            this.last = last;
        }
        protected Integer compute() {
            int subCount;
            if (last - first < 10) {
                subCount = 0;
                for (int i = first; i <= last; i++) {
                    if (d[i] < 0.5)
                                            subCount++;
                }
            } else {
                int mid = (first + last) >>> 1;
                ForkJoinTask left = new ForkJoinTask(first, mid);
                left.fork();
                ForkJoinTask right = new ForkJoinTask(mid + 1, last);
                right.fork();
                subCount = left.join();
                subCount += right.join();
            }
            return subCount;
        }
    }
    public static void main(String[] args) {
        d = createArrayOfRandomDoubles();
        int n = new ForkJoinPool().invoke(new ForkJoinTask(0, 9999999));
        System.out.println("Found " + n + " values");
    }
}

以上的關(guān)鍵是fork()和join()方法。在ForkJoinPool使用的線程中,會使用一個內(nèi)部隊列來對需要執(zhí)行的任務(wù)以及子任務(wù)進行操作來保證它們的執(zhí)行順序。

那么使用ThreadPoolExecutor或者ForkJoinPool,會有什么性能的差異呢?

首先,使用ForkJoinPool能夠使用數(shù)量有限的線程來完成非常多的具有父子關(guān)系的任務(wù),比如使用4個線程來完成超過200萬個任務(wù)。但是,使用ThreadPoolExecutor時,是不可能完成的,因為ThreadPoolExecutor中的Thread無法選擇優(yōu)先執(zhí)行子任務(wù),需要完成200萬個具有父子關(guān)系的任務(wù)時,也需要200萬個線程,顯然這是不可行的。

當(dāng)然,在上面的例子中,也可以不使用分治法,因為任務(wù)之間的獨立性,可以將整個數(shù)組劃分為幾個區(qū)域,然后使用ThreadPoolExecutor來解決,這種辦法不會創(chuàng)建數(shù)量龐大的子任務(wù)。代碼如下:

public class ThreadPoolTest {
    private double[] d;
    private class ThreadPoolExecutorTask implements Callable<Integer> {
        private int first;
        private int last;
        public ThreadPoolExecutorTask(int first, int last) {
            this.first = first;
            this.last = last;
        }
        public Integer call() {
            int subCount = 0;
            for (int i = first; i <= last; i++) {
                if (d[i] < 0.5) {
                    subCount++;
                }
            }
            return subCount;
        }
    }
    public static void main(String[] args) {
        d = createArrayOfRandomDoubles();
        ThreadPoolExecutor tpe = new ThreadPoolExecutor
        (4, 4, long.MAX_VALUE, TimeUnit.SECONDS, new LinkedBlockingQueue());
        Future[] f = new Future[4];
        int size = d.length / 4;
        for (int i = 0; i < 3; i++) {
            f[i] = tpe.submit(new ThreadPoolExecutorTask(i * size, (i + 1) * size - 1);
        }
        f[3] = tpe.submit(new ThreadPoolExecutorTask(3 * size, d.length - 1);
        int n = 0;
        for (int i = 0; i < 4; i++) {
            n += f.get();
        }
        System.out.println("Found " + n + " values");
    }
}

在分別使用ForkJoinPool和ThreadPoolExecutor時,它們處理這個問題的時間如下:

Java并發(fā)編程:線程及同步的性能——線程池

對執(zhí)行過程中的GC同樣也進行了監(jiān)控,發(fā)現(xiàn)在使用ForkJoinPool時,總的GC時間花去了1.2s,而ThreadPoolExecutor并沒有觸發(fā)任何的GC操作。這是因為在ForkJoinPool的運行過程中,會創(chuàng)建大量的子任務(wù)。而當(dāng)他們執(zhí)行完畢之后,會被垃圾回收。反之,ThreadPoolExecutor則不會創(chuàng)建任何的子任務(wù),因此不會導(dǎo)致任何的GC操作。

ForkJoinPool的另外一個特性是它能夠?qū)崿F(xiàn)工作竊取(Work Stealing),在該線程池的每個線程中會維護一個隊列來存放需要被執(zhí)行的任務(wù)。當(dāng)線程自身隊列中的任務(wù)都執(zhí)行完畢后,它會從別的線程中拿到未被執(zhí)行的任務(wù)并幫助它執(zhí)行。

可以通過以下的代碼來測試ForkJoinPool的Work Stealing特性:

for (int i = first; i <= last; i++) {
    if (d[i] < 0.5) {
        subCount++;
    }
    for (int j = 0; j < d.length - i; j++) {
        for (int k = 0; k < 100; k++) {
            dummy = j * k + i;
            // dummy is volatile, so multiple writes occur
            d[i] = dummy;
        }
    }
}

因為里層的循環(huán)次數(shù)(j)是依賴于外層的i的值的,所以這段代碼的執(zhí)行時間依賴于i的值。當(dāng)i = 0時,執(zhí)行時間最長,而i = last時執(zhí)行時間最短。也就意味著任務(wù)的工作量是不一樣的,當(dāng)i的值較小時,任務(wù)的工作量大,隨著i逐漸增加,任務(wù)的工作量變小。因此這是一個典型的任務(wù)負載不均衡的場景。

這時,選擇ThreadPoolExecutor就不合適了,因為它其中的線程并不會關(guān)注每個任務(wù)之間任務(wù)量的差異。當(dāng)執(zhí)行任務(wù)量最小的任務(wù)的線程執(zhí)行完畢后,它就會處于空閑的狀態(tài)(Idle),等待任務(wù)量最大的任務(wù)執(zhí)行完畢。

而ForkJoinPool的情況就不同了,即使任務(wù)的工作量有差別,當(dāng)某個線程在執(zhí)行工作量大的任務(wù)時,其他的空閑線程會幫助它完成剩下的任務(wù)。因此,提高了線程的利用率,從而提高了整體性能。

這兩種線程池對于任務(wù)工作量不均衡時的執(zhí)行時間:

Java并發(fā)編程:線程及同步的性能——線程池

注意到當(dāng)線程數(shù)量為1時,兩者的執(zhí)行時間差異并不明顯。這是因為總的計算量是相同的,而ForkJoinPool慢的那一秒多是因為它創(chuàng)建了非常多的任務(wù),同時也導(dǎo)致了GC的工作量增加。

當(dāng)線程數(shù)量增加到4時,執(zhí)行時間的區(qū)別就較大了,F(xiàn)orkJoinPool的性能比ThreadPoolExecutor好將近50%,可見Work Stealing在應(yīng)對任務(wù)量不均衡的情況下,能夠保證資源的利用率。

所以一個結(jié)論就是:當(dāng)任務(wù)的任務(wù)量均衡時,選擇ThreadPoolExecutor往往更好,反之則選擇ForkJoinPool。

另外,對于ForkJoinPool,還有一個因素會影響它的性能,就是停止進行任務(wù)分割的那個閾值。比如在之前的快速排序中,當(dāng)剩下的元素數(shù)量小于10的時候,就會停止子任務(wù)的創(chuàng)建。下表顯示了在不同閾值下,F(xiàn)orkJoinPool的性能:

Java并發(fā)編程:線程及同步的性能——線程池

可以發(fā)現(xiàn),當(dāng)閾值不同時,對于性能也會有一定影響。因此,在使用ForkJoinPool時,對此閾值進行測試,使用一個最合適的值也有助于整體性能。

自動并行化(Automatic Parallelization)

在Java 8中,引入了自動并行化的概念。它能夠讓一部分Java代碼自動地以并行的方式執(zhí)行,前提是使用了ForkJoinPool。

Java 8為ForkJoinPool添加了一個通用線程池,這個線程池用來處理那些沒有被顯式提交到任何線程池的任務(wù)。它是ForkJoinPool類型上的一個靜態(tài)元素,它擁有的默認(rèn)線程數(shù)量等于運行計算機上的處理器數(shù)量。

當(dāng)調(diào)用Arrays類上添加的新方法時,自動并行化就會發(fā)生。比如用來排序一個數(shù)組的并行快速排序,用來對一個數(shù)組中的元素進行并行遍歷。自動并行化也被運用在Java 8新添加的Stream API中。

比如下面的代碼用來遍歷列表中的元素并執(zhí)行需要的計算:

Stream<Integer> stream = arrayList.parallelStream();
stream.forEach(a -> {
    String symbol = StockPriceUtils.makeSymbol(a);
    StockPriceHistory sph = new StockPriceHistoryImpl(symbol, startDate, endDate, entityManager);
}
);

對于列表中的元素的計算都會以并行的方式執(zhí)行。forEach方法會為每個元素的計算操作創(chuàng)建一個任務(wù),該任務(wù)會被前文中提到的ForkJoinPool中的通用線程池處理。以上的并行計算邏輯當(dāng)然也可以使用ThreadPoolExecutor完成,但是就代碼的可讀性和代碼量而言,使用ForkJoinPool明顯更勝一籌。

對于ForkJoinPool通用線程池的線程數(shù)量,通常使用默認(rèn)值就可以了,即運行時計算機的處理器數(shù)量。如果需要調(diào)整線程數(shù)量,可以通過設(shè)置系統(tǒng)屬性:-Djava.util.concurrent.ForkJoinPool.common.parallelism=N

下面的一組數(shù)據(jù)用來比較使用ThreadPoolExecutor和ForkJoinPool中的通用線程池來完成上面簡單計算時的性能:

Java并發(fā)編程:線程及同步的性能——線程池

注意到當(dāng)線程數(shù)為1,2,4時,性能差異的比較明顯。線程數(shù)為1的ForkJoinPool通用線程池和線程數(shù)為2的ThreadPoolExecutor的性能十分接近。

出現(xiàn)這種現(xiàn)象的原因是,forEach方法用了一些小把戲。它會將執(zhí)行forEach本身的線程也作為線程池中的一個工作線程。因此,即使將ForkJoinPool的通用線程池的線程數(shù)量設(shè)置為1,實際上也會有2個工作線程。因此在使用forEach的時候,線程數(shù)為1的ForkJoinPool通用線程池和線程數(shù)為2的ThreadPoolExecutor是等價的。

所以當(dāng)ForkJoinPool通用線程池實際需要4個工作線程時,可以將它設(shè)置成3,那么在運行時可用的工作線程就是4了。

總結(jié)

  1. 當(dāng)需要處理遞歸分治算法時,考慮使用ForkJoinPool。
  2. 仔細設(shè)置不再進行任務(wù)劃分的閾值,這個閾值對性能有影響。
  3. Java 8中的一些特性會使用到ForkJoinPool中的通用線程池。在某些場合下,需要調(diào)整該線程池的默認(rèn)的線程數(shù)量。

————END————

Java并發(fā)編程:線程及同步的性能——線程池

分享標(biāo)題:Java并發(fā)編程:線程及同步的性能——線程池
分享網(wǎng)址:http://jinyejixie.com/article8/pgisop.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供、軟件開發(fā)、App設(shè)計、品牌網(wǎng)站建設(shè)、用戶體驗、云服務(wù)器

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

成都app開發(fā)公司
清远市| 巴林左旗| 会泽县| 磴口县| 西峡县| 太仓市| 永嘉县| 盐山县| 四川省| 乌兰浩特市| 梨树县| 班玛县| 镇沅| 仪陇县| 旌德县| 石棉县| 彰武县| 班玛县| 昌吉市| 新民市| 进贤县| 龙口市| 电白县| 安化县| 重庆市| 华蓥市| 丹棱县| 东平县| 五河县| 耿马| 巴马| 阿拉善左旗| 莲花县| 富锦市| 大名县| 托克逊县| 石柱| 邛崃市| 浑源县| 大方县| 新民市|