• <nav id="wkkge"><strong id="wkkge"></strong></nav>
  • <menu id="wkkge"></menu>
  • 第一部分 Java基礎
    第二部分 Java進階

    Java多線程筆試題

     

     

    說明:答案要變成自己的話去描述,不要死記硬背。

     


    多線程專題

     

    ● 在實際開發中用過多線程嗎,具體怎么用的,解決什么問題?


    我們通常在后臺執行定時任務的時候會用到多線程,例如我們在P2P項目中給用戶回款的時候,數據量比較大,收益需要在指定的時間之前全部返還,不然客戶這邊投訴的電話就來了,當時我們使用了JUC包下的Excutor線程池,啟動多線程跑數據。這樣問題就解決了。


    ● 你對java的內存模型有了解嗎?


    面試官問這個問題的時候,同學們要注意了,面試官要聽的不是“JVM的內存結構”,而是Java的內存模型,簡稱JMM(Java Memory Model),參見另一個文件:Java內存模型JMM.docx


    ● 線程之間怎么通信?


    另請參見:Java線程間的通信.docx


    ● 什么是線程安全,怎么理解的?


    如果你的代碼在多線程下執行和在單線程下執行永遠都能獲得一樣的結果,那么你的代碼就是線程安全的。


    導致線程不安全的原因是:多線程同時對某個共享的數據進行修改操作,此時就會存在數據不一致問題。在Java中線程安全也是有幾個級別的:


    不可變


    像String,其中String字符串在JVM中有字符串常量池的存在,字符串對象一旦創建不可變,由于這些對象在多線程并發的情況下不會被修改,所以不存在線程安全問題。


    絕對線程安全


    不管運行時環境如何,調用者都不需要額外的同步措施。要做到這一點通常需要付出許多額外的代價,Java中標注自己是線程安全的類,實際上絕大多數都不是線程安全的,不過絕對線程安全的類,Java中也有,比方說CopyOnWriteArrayList、CopyOnWriteArraySet。


    相對線程安全


    相對線程安全也就是我們通常意義上所說的線程安全,像Vector這種,add、remove方法都是原子操作,不會被打斷,但也僅限于此,如果有個線程在遍歷某個Vector、有個線程同時在add這個Vector,99%的情況下都會出現ConcurrentModificationException,也就是 fail-fast機制。


    非線程安全


    這個就沒什么好說的了,ArrayList、LinkedList、HashMap等都是線程非安全的類。


    ● 線程之間如何共享數據?


    (1)多個線程對共享數據的操作是相同的,那么創建一個Runnable的子類對象,將這個對象作為參數傳遞給Thread的構造方法,此時因為多個線程操作的是同一個Runnable的子類對象,所以他們操作的是同一個共享數據。比如:買票系統,所以的線程的操作都是對票數減一的操作。


    (2)多個線程對共享數據的操作是不同的,將共享數據和操作共享數據的方法放在同一對象中,將這個對象作為參數傳遞給Runnable的子類,在子類中用該對象的方法對共享數據進行操作。比如實現生產者和消費者模型。


    (3)多個線程對共享數據的操作是不同的, 用內部類的方式去實現,創建Runnable的子類作為內部類,將共享對象作為全局變量,在Runnable的子類中對共享數據進行操作。


    ● 線程的start()和run()方法的區別?


    start()方法表示啟動一個新的線程,在JVM內存中會開啟一個新的棧空間。而run()方法只是普通方法調用,不會啟動新的線程。


    ● 實現線程的方式分別是什么?


    第一種:繼承Thread


    第二種:實現Runnable接口,這種方式使用較多,面向接口編程一直是被推崇的開發原則。


    第三種:實現Callable接口用來實現返回結果的線程


    ● 怎么獲取線程的返回值?


    使用ExecutorService、Callable、Future實現有返回結果的線程。可返回值的任務必須實現Callable接口。執行Callable任務后,可以獲取一個Future的對象,在該對象上調用get就可以獲取到Callable任務返回的Object了。get方法是阻塞的,即:線程無返回結果,get方法會一直等待。再結合線程池接口ExecutorService就可以實現傳說中有返回結果的多線程了。


    ● volatile關鍵字的作用是什么?


    參見《java并發編程之volatile關鍵字解析.docx》


    ● CyclicBarrier(籬柵)和CountDownLatch(倒計時鎖存器)的區別是什么?


    都在java.util.concurrent下,都可以用來表示代碼運行到某個點上,二者的區別在于:


    (1)CyclicBarrier的某個線程運行到某個點上之后,該線程即停止運行,直到所有的線程都到達了這個點,所有線程才重新運行;

     

    CountDownLatch則不是,某線程運行到某個點上之后,只是給某個數值-1而已,該線程繼續運行。


    (2)CyclicBarrier只能喚起一個任務,CountDownLatch可以喚起多個任務。


    (3)CyclicBarrier可重用,CountDownLatch不可重用,計數值為0該CountDownLatch就不可再用了。


    ● volatile(不穩定的)和synchronized(同步的)對比?


    讀以下內容之前,參見:《java并發編程之volatile關鍵字解析.docx》
    一旦一個共享變量(類的成員變量、類的靜態成員變量)被volatile修飾之后,那么就具備了兩層語義:


    第一:保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。


    第二:禁止進行指令重排序。


    volatile本質是在告訴jvm當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀取;


    (1)synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。


    (2)volatile僅能使用在變量級別;synchronized則可以使用在變量、方法、和類級別的。


    (3)volatile僅能實現變量的修改可見性,并不能保證原子性;synchronized則可以保證變量的修改可見性和原子性。


    (4)volatile不會造成線程的阻塞;synchronized可能會造成線程的阻塞。


    (5)volatile標記的變量不會被編譯器優化;synchronized標記的變量可以被編譯器優化。


    ● 怎么喚醒一個阻塞的線程?


    如果線程是因為調用了wait()、sleep()或者join()方法而導致的阻塞,可以中斷線程,并且通過拋出InterruptedException來喚醒它;如果線程遇到了IO阻塞,無能為力,因為IO是操作系統實現的,Java代碼并沒有辦法直接接觸到操作系統。


    ● Java中如何獲取到線程dump文件?


    當程序遇到死循環、死鎖、阻塞、頁面打開慢等問題,查看dump信息是最好的解決問題的途徑。線程dump也就是線程堆棧信息。
    獲取到線程堆棧dump文件內容分兩步:


    (1)第一步:獲取到線程的pid,Linux環境下可以使用ps -ef | grep java
    (2)第二步:打印線程堆棧,可以通過使用jstack pid命令
    具體實現步驟,參照:https://blog.csdn.net/u010271462/article/details/70171553


    ● sleep方法和wait方法的相同點和不同點?


    相同點


    二者都可以讓線程處于凍結狀態。


    不同點


    (1)首先應該明確sleep方法是Thread類中定義的方法,而wait方法是Object類中定義的方法。


    (2)sleep方法必須人為地為其指定時間。wait方法既可以指定時間,也可以不指定時間。


    (3)sleep方法時間到,線程處于臨時阻塞狀態或者運行狀態。wait方法如果沒有被設置時間,就必須要通過notify或者notifyAll來喚醒。


    (4)sleep方法不一定非要定義在同步中。wait方法必須定義在同步中。


    (5)當二者都定義在同步中時,線程執行到sleep,不會釋放鎖。線程執行到wait,會釋放鎖。


    ● 生產者和消費者模型的作用是什么?


    (1)通過平衡生產者的生產能力和消費者的消費能力來提升整個系統的運行效率,這是生產者消費者模型最重要的作用


    (2)解耦,這是生產者消費者模型附帶的作用,解耦意味著生產者和消費者之間的聯系少,聯系越少越可以獨自發展而不需要受到相互的制約。


    ● ThreadLocal有什么作用?


    ThreadLocal用來解決多線程程序的并發問題。將數據放到ThreadLocal當中可以將該數據和當前線程綁定在一起,這樣可以保證一個線程對應一個對象,這樣對象就是線程安全的。


    ● wait方法和notify/notifyAll方法在放棄對象監視器時有什么區別?


    wait()方法立即釋放對象監視器;


    notify()/notifyAll()方法則會等待線程剩余代碼執行完畢才會放棄對象監視器。


    ● Lock和synchronized對比?


    (1)Lock是一個接口,而synchronized是Java中的關鍵字,synchronized是內置的語言實現;


    (2)synchronized在發生異常時,會自動釋放線程占有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖;


    (3)Lock可以讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷;


    (4)通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。


    (5)Lock可以提高多個線程進行讀操作的效率。


    (6)在JDK1.5中,synchronized是性能低效的。因為這是一個重量級操作,它對性能最大的影響是阻塞式的實現,掛起線程和恢復線程的操作都需要轉入內核態中完成,這些操作給系統的并發性帶來了很大的壓力。相比之下使用Java提供的Lock對象,性能更高一些。但是,JDK1.6,發生了變化,對synchronize加入了很多優化措施,有自適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導致在JDK1.6上synchronize的性能并不比Lock差。因此。提倡優先考慮使用synchronized來進行同步。


    ● ConcurrentHashMap的并發度是什么?


    ConcurrentHashMap的并發度就是segment的大小,默認為16,這意味著最多同時可以有16條線程操作ConcurrentHashMap,這也是ConcurrentHashMap對Hashtable的最大優勢。


    ● ReadWriteLock是什么?


    首先明確一下,不是說ReentrantLock不好,只是ReentrantLock某些時候有局限。如果使用ReentrantLock,可能本身是為了防止線程A在寫數據、線程B在讀數據造成的數據不一致,但這樣,如果線程C在讀數據、線程D也在讀數據,讀數據是不會改變數據的,沒有必要加鎖,但是還是加鎖了,降低了程序的性能。因為這個,才誕生了讀寫鎖ReadWriteLock。ReadWriteLock是一個讀寫鎖接口,ReentrantReadWriteLock是ReadWriteLock接口的一個具體實現,實現了讀寫的分離, 讀鎖是共享的,寫鎖是獨占的,讀和讀之間不會互斥,讀和寫、寫和讀、寫和寫之間才會互斥,提升了讀寫的性能。


    ● 不可變對象對多線程有什么幫助


    不可變對象保證了對象的內存可見性,對不可變對象的讀取不需要進行額外的同步手段,提升了代碼執行效率。


    ● FutureTask是什么?


    FutureTask表示一個異步運算的任務。FutureTask里面可以傳入一個Callable的具體實現類,可以對這個異步運算的任務的結果進行等待獲取、判斷是否已經完成、取消任務等操作。當然,由于FutureTask也是Runnable接口的實現類,所以FutureTask也可以放入線程池中。


    ● Java中用到的線程調度算法是什么?


    搶占式。一個線程用完CPU之后,操作系統會根據線程優先級、線程饑餓情況等數據算出一個總的優先級并分配下一個時間片給某個線程執行。


    ● 怎么得到一個線程安全的單例模式?


    餓漢本來就是線程安全的,就不再多說了。懶漢單例本身就是非線程安全的,可以使用雙檢鎖的方式實現線程安全的單例模式。雙檢鎖就是volatile+synchronized實現。


    ● 什么是樂觀鎖和悲觀鎖?


    (1)樂觀鎖:樂觀鎖(Optimistic Lock), 顧名思義,就是很樂觀,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號等機制。樂觀鎖適用于多讀的應用類型,這樣可以提高吞吐量,像數據庫如果提供類似于write_condition機制的其實都是提供的樂觀鎖。

     

    (2)悲觀鎖:對于并發間操作產生的線程安全問題持悲觀狀態,悲觀鎖認為競爭總是會發生,因此每次對某資源進行操作時,都會持有一個獨占的鎖,就像synchronized,不管三七二十一,直接上了鎖就操作資源了。


    ● Java編寫一個會導致死鎖的程序?


    (1)兩個線程里面分別持有兩個Object對象:lock1和lock2。這兩個lock作為同步代碼塊的鎖;


    (2)線程1的run()方法中同步代碼塊先獲取lock1的對象鎖,Thread.sleep(xxx),時間不需要太多,50毫秒差不多了,然后接著獲取lock2的對象鎖。這么做主要是為了防止線程1啟動一下子就連續獲得了lock1和lock2兩個對象的對象鎖;


    (3)線程2的run)(方法中同步代碼塊先獲取lock2的對象鎖,接著獲取lock1的對象鎖,當然這時lock1的對象鎖已經被線程1鎖持有,線程2肯定是要等待線程1釋放lock1的對象鎖的。


    這樣,線程1”睡覺”睡完,線程2已經獲取了lock2的對象鎖了,線程1此時嘗試獲取lock2的對象鎖,便被阻塞,此時一個死鎖就形成了。


    ● 如何確保N個線程可以同時訪問N個資源又不導致死鎖?


    使用多線程的時候,一種非常簡單的避免死鎖的方式就是:指定獲取鎖的順序,并強制線程按照指定的順序獲取鎖。因此,如果所有的線程都是以同樣的順序加鎖和釋放鎖,就不會出現死鎖了。


    ● Thread.sleep(0)的作用是什么?


    這個問題和上面那個問題是相關的,我就連在一起了。由于Java采用搶占式的線程調度算法,因此可能會出現某條線程常常獲取到CPU控制權的情況,為了讓某些優先級比較低的線程也能獲取到CPU控制權,可以使用Thread.sleep(0)手動觸發一次操作系統分配時間片的操作,這也是平衡CPU控制權的一種操作。


    ● 高并發、任務執行時間短的業務怎樣使用線程池?并發不高、任務執行時間長的業務怎樣使用線程池?并發高、業務執行時間長的業務怎樣使用線程池?


    (1)高并發、任務執行時間短的業務,線程池線程數可以設置為CPU核數+1,減少線程上下文的切換


    (2)并發不高、任務執行時間長的業務要區分開看:
    假如是業務時間長集中在IO操作上,也就是IO密集型的任務,因為IO操作并不占用CPU,所以不要讓所有的CPU閑下來,可以加大線程池中的線程數目,讓CPU處理更多的業務
    假如是業務時間長集中在計算操作上,也就是計算密集型任務,這個就沒辦法了,和(1)一樣吧,線程池中的線程數設置得少一些,減少線程上下文的切換


    (3)并發高、業務執行時間長,解決這種類型任務的關鍵不在于線程池而在于整體架構的設計,看看這些業務里面某些數據是否能做緩存是第一步,增加服務器是第二步,至于線程池的設置,設置參考(2)。最后,業務執行時間長的問題,也可能需要分析一下,看看能不能使用中間件對任務進行拆分和解耦。


    ● 同步方法和同步塊,哪個是更好的選擇,你怎么看?


    同步塊,這意味著同步塊之外的代碼是異步執行的,這比同步整個方法更提升代碼的效率。請知道一條原則:同步的范圍越少越好。借著這一條,我額外提一點,雖說同步的范圍越少越好,但是在Java虛擬機中還是存在著一種叫做 鎖粗化 的優化方法,這種方法就是把同步范圍變大。這是有用的,比方說StringBuffer,它是一個線程安全的類,自然最常用的append()方法是一個同步方法,我們寫代碼的時候會反復append字符串,這意味著要進行反復的加鎖->解鎖,這對性能不利,因為這意味著Java虛擬機在這條線程上要反復地在內核態和用戶態之間進行切換,因此Java虛擬機會將多次append方法調用的代碼進行一個鎖粗化的操作,將多次的append的操作擴展到append方法的頭尾,變成一個大的同步塊,這樣就減少了加鎖–>解鎖的次數,有效地提升了代碼執行的效率。


    ● 線程類的構造方法、靜態塊是被哪個線程調用的


    這是一個非常刁鉆和狡猾的問題。請記住:線程類的構造方法、靜態塊是被new這個線程類所在的線程所調用的,而run方法里面的代碼才是被線程自身所調用的。如果說上面的說法讓你感到困惑,那么我舉個例子, 假設Thread2中new了Thread1,main函數中new了Thread2,那么:


    (1)Thread2的構造方法、靜態塊是main線程調用的,Thread2的run()方法是Thread2自己調用的


    (2)Thread1的構造方法、靜態塊是Thread2調用的,Thread1的run()方法是Thread1自己調用的


    ● Hashtable的size()方法中明明只有一條語句”return count”,為什么還要做同步?


    這是我之前的一個困惑,不知道大家有沒有想過這個問題。某個方法中如果有多條語句,并且都在操作同一個類變量,那么在多線程環境下不加鎖,勢必會引發線程安全問題,這很好理解,但是size()方法明明只有一條語句,為什么還要加鎖?


    關于這個問題,在慢慢地工作、學習中,有了理解,主要原因有兩點:
    (1) 同一時間只能有一條線程執行固定類的同步方法,但是對于類的非同步方法,可以多條線程同時訪問 。所以,這樣就有問題了,可能線程A在執行Hashtable的put方法添加數據,線程B則可以正常調用size()方法讀取Hashtable中當前元素的個數,那讀取到的值可能不是最新的,可能線程A添加了完了數據,但是沒有對size++,線程B就已經讀取size了,那么對于線程B來說讀取到的size一定是不準確的。而給size()方法加了同步之后,意味著線程B調用size()方法只有在線程A調用put方法完畢之后才可以調用,這樣就保證了線程安全性


    (2) CPU執行代碼,執行的不是Java代碼,這點很關鍵,一定得記住 。Java代碼最終是被翻譯成匯編代碼執行的,匯編代碼才是真正可以和硬件電路交互的代碼。 即使你看到Java代碼只有一行,甚至你看到Java代碼編譯之后生成的字節碼也只有一行,也不意味著對于底層來說這句語句的操作只有一個 。一句”return count”假設被翻譯成了三句匯編語句執行,完全可能執行完第一句,線程就切換了。


    ● 多線程有什么用?


    (1)發揮多核CPU的優勢


    隨著工業的進步,現在的筆記本、臺式機乃至商用的應用服務器至少也都是雙核的,4核、8核甚至16核的也都不少見,如果是單線程的程序,那么在雙核CPU上就浪費了50%,在4核CPU上就浪費了75%。 單核CPU上所謂的”多線程”那是假的多線程,同一時間處理器只會處理一段邏輯,只不過線程之間切換得比較快,看著像多個線程”同時”運行罷了 。多核CPU上的多線程才是真正的多線程,它能讓你的多段邏輯同時工作,多線程,可以真正發揮出多核CPU的優勢來,達到充分利用CPU的目的。


    (2)防止阻塞


    從程序運行效率的角度來看,單核CPU不但不會發揮出多線程的優勢,反而會因為在單核CPU上運行多線程導致線程上下文的切換,而降低程序整體的效率。但是單核CPU我們還是要應用多線程,就是為了防止阻塞。試想,如果單核CPU使用單線程,那么只要這個線程阻塞了,比方說遠程讀取某個數據吧,對端遲遲未返回又沒有設置超時時間,那么你的整個程序在數據返回回來之前就停止運行了。多線程可以防止這個問題,多條線程同時運行,哪怕一條線程的代碼執行讀取數據阻塞,也不會影響其它任務的執行。


    ● Semaphore有什么作用,信號量是什么?


    Semaphore就是一個信號量,它的作用是限制某段代碼塊的并發數。Semaphore有一個構造函數,可以傳入一個int型整數n,表示某段代碼最多只有n個線程可以訪問,如果超出了n,那么請等待,等到某個線程執行完畢這段代碼塊,下一個線程再進入。由此可以看出如果Semaphore構造函數中傳入的int型整數n=1,相當于變成了一個synchronized了。


    ● 一個線程如果出現了運行時異常會怎么樣?


    如果這個異常沒有被捕獲的話,這個線程就停止執行了。另外重要的一點是: 如果這個線程持有某個某個對象的監視器,那么這個對象監視器會被立即釋放。


    ● 為什么使用線程池?


    避免頻繁地創建和銷毀線程,達到線程對象的重用。另外,使用線程池還可以根據項目靈活地控制并發的數目。


    ● synchronized和ReentrantLock的區別


    synchronized是和if、else、for、while一樣的關鍵字,ReentrantLock是類,這是二者的本質區別。既然ReentrantLock是類,那么它就提供了比synchronized更多更靈活的特性,可以被繼承、可以有方法、可以有各種各樣的類變量,ReentrantLock比synchronized的擴展性體現在幾點上:


    (1)ReentrantLock可以對獲取鎖的等待時間進行設置,這樣就避免了死鎖


    (2)ReentrantLock可以獲取各種鎖的信息


    (3)ReentrantLock可以靈活地實現多路通知


    另外,二者的鎖機制其實也是不一樣的。ReentrantLock底層調用的是Unsafe的park方法加鎖,synchronized操作的是對象頭中mark word。


    ● 什么是多線程的上下文切換


    多線程的上下文切換是指CPU控制權由一個已經正在運行的線程切換到另外一個就緒并等待獲取CPU執行權的線程的過程。


    ● 如果你提交任務時,線程池隊列已滿,這時會發生什么


    如果你使用的LinkedBlockingQueue,也就是無界隊列的話,沒關系,繼續添加任務到阻塞隊列中等待執行,因為LinkedBlockingQueue可以近乎認為是一個無窮大的隊列,可以無限存放任務;如果你使用的是有界隊列比方說ArrayBlockingQueue的話,任務首先會被添加到ArrayBlockingQueue中,ArrayBlockingQueue滿了,則會使用拒絕策略RejectedExecutionHandler處理滿了的任務,默認是AbortPolicy。


    ● 什么是CAS?


    CAS,全稱為Compare and Set,即比較-設置。假設有三個操作數: 內存值V、舊的預期值A、要修改的值B,當且僅當預期值A和內存值V相同時,才會將內存值修改為B并返回true,否則什么都不做并返回false 。當然CAS一定要volatile變量配合,這樣才能保證每次拿到的變量是主內存中最新的那個值,否則舊的預期值A對某條線程來說,永遠是一個不會變的值A,只要某次CAS操作失敗,永遠都不可能成功。


    ● 什么是AQS?


    簡單說一下AQS,AQS全稱為AbstractQueuedSychronizer,翻譯過來應該是抽象隊列同步器。


    如果說java.util.concurrent的基礎是CAS的話,那么AQS就是整個Java并發包的核心了,ReentrantLock、CountDownLatch、Semaphore等等都用到了它。AQS實際上以雙向隊列的形式連接所有的Entry,比方說ReentrantLock,所有等待的線程都被放在一個Entry中并連成雙向隊列,前面一個線程使用ReentrantLock好了,則雙向隊列實際上的第一個Entry開始運行。


    AQS定義了對雙向隊列所有的操作,而只開放了tryLock和tryRelease方法給開發者使用,開發者可以根據自己的實現重寫tryLock和tryRelease方法,以實現自己的并發功能。


    ● notify和notifyAll的區別?


    notifyAll使所有原來在該對象上等待被喚醒的線程統統退出wait的狀態,變成等待該對象上的鎖,一旦該對象被解鎖,他們就會去競爭。notify是通知其中一個線程,不會通知所有的線程。


    ● 你是如何合理配置線程池大小的?


    首先,需要考慮到線程池所進行的工作的性質:IO密集型?CPU密集型?
    簡單的分析來看,如果是CPU密集型的任務,我們應該設置數目較小的線程數,比如CPU數目加1。如果是IO密集型的任務,則應該設置可能多的線程數,由于IO操作不占用CPU,所以,不能讓CPU閑下來。當然,如果線程數目太多,那么線程切換所帶來的開銷又會對系統的響應時間帶來影響。


    在《linux多線程服務器端編程》中有一個思路,CPU計算和IO的阻抗匹配原則。如果線程池中的線程在執行任務時,密集計算所占的時間比重為P(0 下面驗證一下邊界條件的正確性:


    假設C = 8,P = 1.0,線程池的任務完全是密集計算,那么T = 8。只要8個活動線程就能讓8個CPU飽和,再多也沒用了,因為CPU資源已經耗光了。


    假設C = 8,P = 0.5,線程池的任務有一半是計算,有一半在等IO上,那么T = 16.考慮操作系統能靈活,合理調度sleeping/writing/running線程,那么大概16個“50%繁忙的線程”能讓8個CPU忙個不停。啟動更多的線程并不能提高吞吐量,反而因為增加上下文切換的開銷而降低性能。


    如果P < 0.2,這個公式就不適用了,T可以取一個固定值,比如 5*C。另外公式里的C不一定是CPU總數,可以是“分配給這項任務的CPU數目”,比如在8核機器上分出4個核來做一項任務,那么C=4
    文章如何合理設置線程池大小里面提到了一個公式:


    最佳線程數目 = ((線程等待時間+線程CPU時間)/線程CPU時間 )* CPU數目


    比如平均每個線程CPU運行時間為0.5s,而線程等待時間(非CPU運行時間,比如IO)為1.5s,CPU核心數為8,那么根據上面這個公式估算得到:((0.5+1.5)/0.5)*8=32。這個公式進一步轉化為:


    最佳線程數目 = (線程等待時間與線程CPU時間之比 + 1)* CPU數目
    可以得出一個結論:


    線程等待時間所占比例越高,需要越多線程。線程CPU時間所占比例越高,需要越少線程。


    以上公式與之前的CPU和IO密集型任務設置線程數基本吻合。


    ● 線程池具體配置參數?


    corePoolSize:核心線程數


    queueCapacity:任務隊列容量(阻塞隊列)


    maxPoolSize:最大線程數


    keepAliveTime:線程空閑時間


    allowCoreThreadTimeout:允許核心線程超時


    rejectedExecutionHandler:任務拒絕處理器

     

    ● 線程池按以下行為執行任務:


    (1)當線程數小于核心線程數時,創建線程。


    (2)當線程數大于等于核心線程數,且任務隊列未滿時,將任務放入任務隊列。


    (3)當線程數大于等于核心線程數,且任務隊列已滿


    若線程數小于最大線程數,創建線程


    若線程數等于最大線程數,拋出異常,拒絕任務

     

    全部教程
  • <nav id="wkkge"><strong id="wkkge"></strong></nav>
  • <menu id="wkkge"></menu>
  • 面对面棋牌游戏