多線程面試題總結

 

 一、什麼是線程和進程?

進程:在操做系統中可以獨立運行,而且做爲資源分配的基本單位。它表示運行中的程序。系統運行一個程序就是一個進程從建立、運行到消亡的過程。html

線程:是一個比進程更小的執行單位,可以完成進程中的一個功能,也被稱爲輕量級進程。一個進程在其執行的過程當中能夠產生多個線程。java

【注】線程與進程不一樣的是:同類的多個線程共享進程的方法區資源,但每一個線程有本身的程序計數器虛擬機棧本地方法棧,因此係統在產生一個線程,或是在各個線程之間做切換工做時,負擔要比進程小得多。git

爲何程序計數器、虛擬機棧和本地方法棧是線程私有的呢?爲何堆和方法區是線程共享的呢?算法

  • 程序計數器爲何是私有的?數據庫

程序計數器主要有下面兩個做用:小程序

    1. 字節碼解釋器經過改變程序計數器來依次讀取指令,從而實現代碼的流程控制,如:順序執行、選擇、循環、異常處理。
    2. 在多線程的狀況下,程序計數器用於記錄當前線程執行的位置,從而當線程被切換回來的時候可以知道該線程上次運行到哪兒了。

(須要注意的是,若是執行的是 native 方法,那麼程序計數器記錄的是 undefined 地址,只有執行的是 Java 代碼時程序計數器記錄的纔是下一條指令的地址。)數組

因此,程序計數器私有主要是爲了線程切換後能恢復到正確的執行位置安全

  •  虛擬機棧和本地方法棧爲何是私有的?
  • 虛擬機棧: 每一個 Java 方法在執行的同時會建立一個棧幀用於存儲局部變量表、操做數棧、常量池引用等信息。從方法調用直至執行完成的過程,就對應着一個棧幀在 Java 虛擬機棧中入棧和出棧的過程
  • 本地方法棧: 和虛擬機棧所發揮的做用很是類似,區別是: 虛擬機棧爲虛擬機執行 Java 方法 (也就是字節碼)服務,而本地方法棧則爲虛擬機使用到的 Native 方法服務。 在 HotSpot 虛擬機中和 Java 虛擬機棧合二爲一。

因此,爲了保證線程中的局部變量不被別的線程訪問到,虛擬機棧和本地方法棧是線程私有的。服務器

 

堆和方法區是全部線程共享的資源,其中:多線程

  • 堆是進程中最大的一塊內存,主要用於存放新建立的對象 (全部對象都在這裏分配內存)
  • 方法區主要用於存放已被加載的類信息、常量、靜態變量、即時編譯器編譯後的代碼等數據

 二、什麼是上下文切換?

  即便單核處理器也支持多線程執行代碼,CPU經過給每一個線程分配CPU時間片來實現這個機制。時間片是CPU分配給各個線程的時間,由於時間片很是短,因此CPU經過不停地切換線程執行,讓咱們感受多個線程是同時執行的。(時間片通常是幾十毫秒)

  CPU經過時間片分配算法來循環執行任務,當前任務執行一個時間片後會切換到下一個任務。可是,在切換前會保存上一個任務的狀態,以便下次切換回這個任務時,能夠再加載這個任務的狀態。因此任務從保存到加載的過程就是一次上下文切換上下文切換會影響多線程的執行速度

 三、併發與並行?

   併發指的是多個任務交替進行,並行則是指真正意義上的「同時進行」

  實際上,若是系統內只有一個CPU,使用多線程時,在真實系統環境下不能並行,只能經過切換時間片的方式交替進行,從而併發執行任務。真正的並行只能出如今擁有多個CPU的系統中。

 四、線程的生命週期和狀態?(重要!)

 Java 線程在運行的生命週期中的指定時刻只可能處於下面 6 種不一樣狀態的其中一個狀態:

  初始狀態、運行狀態、阻塞狀態、等待狀態、超時等待狀態、終止狀態

線程在生命週期中並非固定處於某一個狀態而是隨着代碼的執行在不一樣狀態之間切換

  • 線程建立以後它將處於 初始狀態(NEW),調用 start() 方法後開始運行,線程這時候處於 可運行狀態(READY)。
  • 可運行狀態的線程得到了 CPU 時間片後就處於 運行狀態(RUNNING)。
  • 線程執行 wait()方法以後,線程進入 等待狀態(WAITING),進入等待狀態的線程須要依靠其餘線程的通知纔可以返回到運行狀態【notify()】。 而 超時等待狀態(TIME_WAITING)至關於在等待狀態的基礎上增長了超時限制,【sleep(long millis)/wait(long millis)】,當超時時間到達後 Java 線程將會返回到運行狀態
  • 當線程調用同步方法時,在沒有獲取到鎖的狀況下,線程將會進入到阻塞狀態(BLOCKED)。
  • 線程在執行 Runnable 的run()方法以後將會進入到 終止狀態(TERMINATED)。

五、什麼是線程死鎖?如何避免死鎖?

   多個線程同時被阻塞,它們中的一個或者所有都在等待某個資源被釋放。因爲線程被無限期地阻塞,所以程序不可能正常終止。

  假如線程 A 持有資源 2,線程 B 持有資源 1,他們同時都想申請對方的資源,因此這兩個線程就會互相等待而進入死鎖狀態。

避免死鎖的幾個常見方法:

  • 避免一個線程同時獲取多個鎖
  • 避免一個線程在鎖內同時佔用多個資源,儘可能保證每一個鎖只佔用一個資源。
  • 嘗試使用定時鎖,使用 lock.tryLock(timeout) 來代替使用內部鎖機制。
  • 對於數據庫鎖,加鎖和解鎖必須在一個數據庫鏈接裏,不然會出現解鎖失敗的狀況。

 六、sleep() 方法和 wait() 方法區別和共同點?(重要!)

 相同點:

  二者均可以暫停線程的執行,都會讓線程進入等待狀態。

不一樣點:

  • sleep()方法沒有釋放鎖,而 wait()方法釋放了鎖。
  • sleep()方法屬於Thread類的靜態方法,做用於當前線程;而wait()方法是Object類的實例方法,做用於對象自己。
  • 執行sleep()方法後,能夠經過超時或者調用interrupt()方法喚醒休眠中的線程;執行wait()方法後,經過調用notify()或notifyAll()方法喚醒等待線程。

七、爲何咱們調用 start() 方法時會執行 run() 方法,爲何咱們不能直接調用 run() 方法?

  new 一個 Thread,線程進入初始狀態;調用 start()方法,會啓動一個線程並使線程進入了就緒狀態,當分配到時間片後就能夠開始運行了。 start() 會執行線程的相應準備工做,而後自動執行 run() 方法的內容,這是真正的多線程工做。 而直接執行 run() 方法,會把 run 方法當成一個 main 線程下的普通方法去執行,並不會在某個線程中執行它,因此這並非多線程工做

總結: 調用 start 方法可啓動線程並使線程進入就緒狀態,而 run 方法只是 thread 的一個普通方法調用,仍是在主線程裏執行

八、多線程開發帶來的問題與解決方法?(重要)

使用多線程主要會帶來如下幾個問題:

(一)線程安全問題

  線程安全問題指的是在某一線程從開始訪問到結束訪問某一數據期間,該數據被其餘的線程所修改,那麼對於當前線程而言,該線程就發生了線程安全問題,表現形式爲數據的缺失,數據不一致等

  線程安全問題發生的條件:

    1)多線程環境下,即存在包括本身在內存在有多個線程。

    2)多線程環境下存在共享資源,且多線程操做該共享資源。

    3)多個線程必須對該共享資源有非原子性操做。

  線程安全問題的解決思路:

    1)儘可能不使用共享變量,將沒必要要的共享變量變成局部變量來使用

    2)使用synchronized關鍵字同步代碼塊,或者使用jdk包中提供的Lock爲操做進行加鎖

    3)使用ThreadLocal爲每個線程創建一個變量的副本,各個線程間獨立操做,互不影響

(二)性能問題

  線程的生命週期開銷是很是大的,一個線程的建立到銷燬都會佔用大量的內存。同時若是不合理的建立了多個線程,cup的處理器數量小於了線程數量,那麼將會有不少的線程被閒置,閒置的線程將會佔用大量的內存,爲垃圾回收帶來很大壓力,同時cup在分配線程時還會消耗其性能。

  解決思路:

  利用線程池,模擬一個池,預先建立有限合理個數的線程放入池中,當須要執行任務時從池中取出空閒的先去執行任務,執行完成後將線程歸還到池中,這樣就減小了線程的頻繁建立和銷燬,節省內存開銷和減少了垃圾回收的壓力。同時由於任務到來時自己線程已經存在,減小了建立線程時間,提升了執行效率,並且合理的建立線程池數量還會使各個線程都處於忙碌狀態,提升任務執行效率,線程池還提供了拒絕策略,當任務數量到達某一臨界區時,線程池將拒絕任務的進入,保持現有任務的順利執行,減小池的壓力。

(三)活躍性問題

  1)死鎖假如線程 A 持有資源 2,線程 B 持有資源 1,他們同時都想申請對方的資源,因此這兩個線程就會互相等待而進入死鎖狀態。多個線程環形佔用資源也是同樣的會產生死鎖問題。

  解決方法:

  • 避免一個線程同時獲取多個鎖
  • 避免一個線程在鎖內同時佔用多個資源,儘可能保證每一個鎖只佔用一個資源。
  • 嘗試使用定時鎖,使用 lock.tryLock(timeout) 來代替使用內部鎖機制。

  想要避免死鎖,能夠使用無鎖函數(cas)或者使用重入鎖(ReentrantLock),經過重入鎖使線程中斷或限時等待能夠有效的規避死鎖問題

  2)飢餓,飢餓指的是某一線程或多個線程由於某些緣由一直獲取不到資源,致使程序一直沒法執行。如某一線程優先級過低致使一直分配不到資源,或者是某一線程一直佔着某種資源不放,致使該線程沒法執行等。

  解決方法:

  與死鎖相比,飢餓現象仍是有可能在一段時間以後恢復執行的。能夠設置合適的線程優先級來儘可能避免飢餓的產生

  3)活鎖,活鎖體現了一種謙讓的美德,每一個線程都想把資源讓給對方,可是因爲機器「智商」不夠,可能會產生一直將資源讓來讓去,致使資源在兩個線程間跳動而沒法使某一線程真正的到資源並執行,這就是活鎖的問題。

(四)阻塞

  阻塞是用來形容多線程的問題,幾個線程之間共享臨界區資源,那麼當一個線程佔用了臨界區資源後,全部須要使用該資源的線程都須要進入該臨界區等待,等待會致使線程掛起,一直不能工做,這種狀況就是阻塞若是某一線程一直都不釋放資源,將會致使其餘全部等待在這個臨界區的線程都不能工做。當咱們使用synchronized或重入鎖時,咱們獲得的就是阻塞線程,如論是synchronized或者重入鎖,都會在試圖執行代碼前,獲得臨界區的鎖,若是得不到鎖,線程將會被掛起等待,知道其餘線程執行完成並釋放鎖且拿到鎖爲止。

  解決方法:

  能夠經過減小鎖持有時間,讀寫鎖分離,減少鎖的粒度,鎖分離,鎖粗化等方式來優化鎖的性能

臨界區:

  臨界區是用來表示一種公共的資源(共享數據),它能夠被多個線程使用,可是在每次只能有一個線程可以使用它,當臨界區資源正在被一個線程使用時,其餘的線程就只能等待當前線程執行完以後才能使用該臨界區資源。

  好比辦公室辦公室裏有一支筆,它一次只能被一我的使用,假如它正在被甲使用時,其餘想要使用這支筆的人只能等甲使用完這支筆以後才能容許另外一我的去使用。這就是臨界區的概念。

參考 http://www.javashuo.com/article/p-vczuwcuu-gs.html

九、 synchronized 關鍵字

synchronized關鍵字能夠保證被它修飾的方法或者代碼塊在任意時刻只能有一個線程執行。

synchronized關鍵字最主要的三種使用方式:修飾實例方法:、修飾靜態方法、修飾代碼塊。

  • 對於普通同步方法,鎖是當前實例對象。
  • 對於靜態同步方法,鎖是當前類的Class對象。
  • 對於同步代碼塊,鎖是synchronized括號裏配置的對象。

  當一個線程試圖訪問同步代碼塊時,它首先必須獲得鎖,退出或拋出異常時必須釋放鎖。

synchronized在JVM裏是怎麼實現的?

  synchronized 同步語句塊的實現使用的是 monitorenter 和 monitorexit 指令,其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結束位置。 當執行 monitorenter 指令時,線程試圖獲取鎖也就是獲取 monitor的持有權。當計數器爲0則能夠成功獲取,獲取後將鎖計數器設爲1也就是加1。相應的在執行 monitorexit 指令後,將鎖計數器設爲0,代表鎖被釋放。若是獲取對象鎖失敗,那當前線程就要阻塞等待,直到鎖被另一個線程釋放爲止。

(monitor對象存在於每一個Java對象的對象頭中,synchronized 鎖即是經過這種方式獲取鎖的,也是爲何Java中任意對象能夠做爲鎖的緣由) 

synchronized用的鎖是存在哪裏的?

  synchronized用到的鎖是存在Java對象頭裏的。

十、說說 JDK1.6 以後的synchronized 關鍵字底層作了哪些優化,能夠詳細介紹一下這些優化嗎

  JDK1.6 對鎖的實現引入了大量的優化,如偏向鎖、輕量級鎖、自旋鎖、適應性自旋鎖、鎖消除、鎖粗化等技術來減小鎖操做的開銷。

  鎖主要存在四種狀態,依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態,他們會隨着競爭的激烈而逐漸升級。注意鎖能夠升級不可降級,這種策略是爲了提升得到鎖和釋放鎖的效率。

  關於這幾種優化的詳細信息能夠查看這篇文章:https://gitee.com/SnailClimb/JavaGuide/blob/master/docs/java/Multithread/synchronized.md

十一、synchronized和 Lock 的區別?(重要)

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

2)synchronized在發生異常時,會自動釋放線程佔有的鎖,所以不會致使死鎖現象發生;而Lock在發生異常時,若是沒有主動經過unLock()去釋放鎖,則極可能形成死鎖現象,所以使用Lock時須要在finally塊中釋放鎖

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

4)經過Lock能夠知道有沒有成功獲取鎖(tryLock()方法:若是獲取鎖成功,則返回true),而synchronized卻沒法辦到

5)Lock能夠提升多個線程進行讀操做的效率

  在性能上來講,若是競爭資源不激烈,二者的性能是差很少的,而當競爭資源很是激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized。因此說,在具體使用時要根據適當狀況選擇。

參考https://blog.csdn.net/qq_38200548/article/details/82943222 

十二、synchronized和ReentrantLock(重入鎖) 的區別?

  • 二者都是可重進入鎖,就是可以支持一個線程對資源的重複加鎖。sychnronized關鍵字隱式的支持重進入,好比一個sychnronized修飾的遞歸方法,在方法執行時,執行線程在獲取了鎖以後仍能連續屢次地獲取該鎖。ReentrantLock雖然沒能像sychnronized關鍵字同樣隱式的重進入,可是在調用lock()方法時,已經獲取到鎖的線程,可以再次調用lock()方法獲取鎖而不被阻塞
    • 線程重複n次獲取了鎖,隨後在第n次釋放該鎖後,其餘線程可以獲取到該鎖。鎖的最終釋放要求鎖對於獲取進行計數自增,計數表示當前鎖被重複獲取的次數,而鎖被釋放時,計數自減,當計數等於0時表示鎖已經成功被釋放
  • synchronized 依賴於 JVM 而 ReentrantLock 依賴於 API。ReentrantLock 是 JDK 層面實現的(也就是 API 層面,須要 lock() 和 unlock() 方法配合 try/finally 語句塊來完成)
  • ReentrantLock 比 synchronized 增長了一些高級功能,主要有3點:①等待可中斷;②可實現公平鎖;③可實現選擇性通知(鎖能夠綁定多個條件)
    • ReentrantLock提供了一種可以中斷等待鎖的線程的機制,也就是說正在等待的線程能夠選擇放棄等待,改成處理其餘事情。經過lock.lockInterruptibly()來實現這個機制。
    • ReentrantLock能夠指定是公平鎖仍是非公平鎖。而synchronized只能是非公平鎖。(公平鎖就是先等待的線程先得到鎖)
    • synchronized關鍵字與wait()和notify()/notifyAll()方法相結合能夠實現等待/通知機制。ReentrantLock類固然也能夠實現,可是須要藉助於Condition接口與newCondition() 方法。用ReentrantLock類結合Condition實例能夠實現「選擇性通知」 。若是執行notifyAll()方法的話就會通知全部處於等待狀態的線程這樣會形成很大的效率問題,而Condition實例的signalAll()方法 只會喚醒註冊在該Condition實例中的全部等待線程

1三、volatile關鍵字

  保證共享變量的「可見性」。可見性的意思是當一個線程修改一個共享變量時,另一個線程能讀到這個修改的值。

  把變量聲明爲volatile,這就指示 JVM每次使用它都到主存中進行讀取。

1四、synchronized 關鍵字和 volatile 關鍵字的區別

  • volatile關鍵字是線程同步的輕量級實現,因此volatile性能比synchronized關鍵字要好。可是volatile關鍵字只能用於變量而synchronized關鍵字能夠修飾方法以及代碼塊
  • 多線程訪問volatile關鍵字不會發生阻塞,而synchronized關鍵字可能會發生阻塞。、
  • volatile關鍵字主要用於解決變量在多個線程之間的可見性,而 synchronized關鍵字解決的是多個線程之間訪問資源的同步性
  • volatile關鍵字能保證數據的可見性,但不能保證數據的原子性。synchronized關鍵字二者都能保證。

1五、使用線程池的好處?

  1. 下降資源消耗。經過重複利用已建立的線程,下降線程建立和銷燬形成的消耗
  2. 提升響應速度當任務到達時,任務能夠不須要等到線程建立就能當即執行
  3. 提升線程的可管理性線程是稀缺資源,若是無限制地建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一分配、調優和監控

1六、說一說幾種常見的線程池及適用場景?(重要)

  能夠建立(Executors.newXXX)3種類型的ThreadPoolExecutor:FixedThreadPoolSingleThreadExecutorCachedThreadPool

  • FixedThreadPool可重用固定線程數的線程池。(適用於負載比較重的服務器)
    • FixedThreadPool使用無界隊列LinkedBlockingQueue做爲線程池的工做隊列
    • 該線程池中的線程數量始終不變。當有一個新的任務提交時,線程池中如有空閒線程,則當即執行。若沒有,則新的任務會被暫存在一個任務隊列中,待有線程空閒時,便處理在任務隊列中的任務。
  • SingleThreadExecutor只會建立一個線程執行任務。(適用於須要保證順序執行各個任務;而且在任意時間點,沒有多線程活動的場景。)
    • SingleThreadExecutorl也使用無界隊列LinkedBlockingQueue做爲工做隊列
    • 若多餘一個任務被提交到該線程池,任務會被保存在一個任務隊列中,待線程空閒,按先入先出的順序執行隊列中的任務。
  • CachedThreadPool:是一個會根據須要調整線程數量的線程池(大小無界,適用於執行不少的短時間異步任務的小程序,或負載較輕的服務器)
    • CachedThreadPool使用沒有容量的SynchronousQueue做爲線程池的工做隊列,但CachedThreadPool的maximumPool是無界的。
    • 線程池的線程數量不肯定,但如有空閒線程能夠複用,則會優先使用可複用的線程。若全部線程均在工做,又有新的任務提交,則會建立新的線程處理任務。全部線程在當前任務執行完畢後,將返回線程池進行復用。
  • ScheduledThreadPool:繼承自ThreadPoolExecutor。它主要用來在給定的延遲以後運行任務,或者按期執行任務。使用DelayQueue做爲任務隊列。

1六、線程池都有哪幾種工做隊列?(重要)

  • ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按FIFO(先進先出)原則對元素進行排序。
  • LinkedBlockingQueue:是一個基於鏈表結構的阻塞隊列,此隊列按FIFO排序元素,吞吐量一般要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列。
  • SynchronousQueue:是一個不存儲元素的阻塞隊列。每一個插入操做必須等到另外一個線程調用移除操做,不然插入操做一直處於阻塞狀態,吞吐量一般要高於Linked-BlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個隊列。
  • PriorityBlockingQueue:一個具備優先級的無限阻塞隊列

1七、建立線程的幾種方式?(重要)

有4種方式:繼承Thread類實現Runnable接口實現Callable接口使用Executor框架來建立線程池

(1)經過繼承Thread類建立線程

public class MyThread extends Thread {//繼承Thread類

  //重寫run方法

  public void run(){

  }

}

----------------------------------------------------------------------------------

public class Main {

  public static void main(String[] args){

    new MyThread().start(); //建立並啓動線程

  }

}

(2)經過實現Runnable接口來建立線程

 

public class MyThread2 implements Runnable {//實現Runnable接口

  //重寫run方法

  public void run(){

  }

}

------------------------------------------------------------------------------------------

public class Main {

  public static void main(String[] args){

    //建立並啓動線程

    MyThread2 myThread=new MyThread2();

    Thread thread=new Thread(myThread);

    thread().start();

    //或者    new Thread(new MyThread2()).start();

  }

}

無論是繼承Thread仍是實現Runnable接口,多線程代碼都是經過運行Thread的start()方法來運行的。

(3)實現Callable接口來建立線程

  與實現Runnable接口相似,和Runnable接口不一樣的是,Callable接口提供了一個call() 方法做爲線程執行體,call()方法比run()方法功能要強大:call()方法能夠有返回值、call()方法能夠聲明拋出異常。

public class Main {

  public static void main(String[] args){

   MyThread3 th=new MyThread3();

   //使用Lambda表達式建立Callable對象

     //使用FutureTask類來包裝Callable對象

   FutureTask<Integer> future=new FutureTask<Integer>(

    (Callable<Integer>)()->{

      return 5;

    }

    );

   new Thread(task,"有返回值的線程").start();//實質上仍是以Callable對象來建立並啓動線程

    try{

    System.out.println("子線程的返回值:"+future.get());//get()方法會阻塞,直到子線程執行結束才返回

    }catch(Exception e){

    ex.printStackTrace();

   }

  }

}

(4)使用Executor框架來建立線程池

  Executors.newXXXX: newFixedThreadPool(int )、newSingleThreadExecutor、newCachedThreadPool、newScheduledThreadPool(int)

   經過Executors的以上四個靜態工廠方法得到 ExecutorService實例然後能夠執行Runnable任務或Callable任務。

  • Executor執行Runnable任務

  經過Executors的以上四個靜態工廠方法得到 ExecutorService實例,然後調用該實例的execute(Runnable command)方法便可。一旦Runnable任務傳遞到execute()方法,該方法便會自動在一個線程上。

public class TestCachedThreadPool{ public static void main(String[] args){ ExecutorService executorService = Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++){ executorService.execute(new TestRunnable()); System.out.println("************* a" + i + " *************"); } executorService.shutdown(); } } class TestRunnable implements Runnable{ 
//重寫run方法
public void run(){ System.out.println(Thread.currentThread().getName() + "線程被調用了。"); }
  • Executor執行Callable任務:

  當將一個Callable的對象傳遞給ExecutorService的submit方法,則該call方法自動在一個線程上執行,而且會返回執行結果Future對象。一樣,將Runnable的對象傳遞給ExecutorService的submit方法,則該run方法自動在一個線程上執行,而且會返回執行結果Future對象,可是在該Future對象上調用get方法,將返回null。

public class CallableDemo{ public static void main(String[] args){ ExecutorService executorService = Executors.newCachedThreadPool(); List<Future<String>> resultList = new ArrayList<Future<String>>(); //建立10個任務並執行 
        for (int i = 0; i < 10; i++){ //使用ExecutorService執行Callable類型的任務,並將結果保存在future變量中 
            Future<String> future = executorService.submit(new TaskWithResult(i)); //將任務執行結果存儲到List中 
 resultList.add(future); } //遍歷任務的結果 
        for (Future<String> fs : resultList){ try{ while(!fs.isDone);//Future返回若是沒有完成,則一直循環等待,直到Future返回完成 
                    System.out.println(fs.get());     //打印各個線程(任務)執行的結果 
                }catch(InterruptedException e){ e.printStackTrace(); }catch(ExecutionException e){ e.printStackTrace(); }finally{ //啓動一次順序關閉,執行之前提交的任務,但不接受新任務 
 executorService.shutdown(); } } } } class TaskWithResult implements Callable<String>{ private int id; public TaskWithResult(int id){ this.id = id; } // 重寫call()方法
    public String call() throws Exception { System.out.println("call()方法被自動調用!!!    " + Thread.currentThread().getName()); //該返回結果將被Future的get方法獲得 
        return "call()方法被自動調用,任務返回的結果是:" + id + "    " + Thread.currentThread().getName(); } } 

 

實現Runnable接口和Callable接口的區別?

 Runnable接口或Callable接口實現類均可以被ThreadPoolExecutor或ScheduledThreadPoolExecutor執行。二者的區別在於 Runnable 接口不會返回結果可是 Callable 接口能夠返回結果

執行execute()方法和submit()方法的區別是什麼呢?

1) execute() 方法用於提交不須要返回值的任務,因此沒法判斷任務是否被線程池執行成功與否;

2) submit() 方法用於提交須要返回值的任務。線程池會返回一個Future類型的對象,經過這個Future對象能夠判斷任務是否執行成功,而且能夠經過future的get()方法來獲取返回值,get()方法會阻塞當前線程直到任務完成,而使用 get(long timeout,TimeUnit unit)方法則會阻塞當前線程一段時間後當即返回,這時候有可能任務沒有執行完。

18.線程池參數?

corePoolSize:線程池的基本大小,當提交一個任務到線程池時,線程池會建立一個線程來執行任務,即便其餘空閒的基本線程可以執行新任務也會建立線程,等到須要執行的任務數大於線程池基本大小時就再也不建立。說白了就是,即使是線程池裏沒有任何任務,也會有corePoolSize個線程在候着等任務。

maximumPoolSize:最大線程數,無論你提交多少任務,線程池裏最多工做線程數就是maximumPoolSize。

keepAliveTime:線程的存活時間。當線程池裏的線程數大於corePoolSize時,若是等了keepAliveTime時長尚未任務可執行,則線程退出。

unit:這個用來指定keepAliveTime的單位,好比秒:TimeUnit.SECONDS。

workQueue:用於保存等待執行任務的阻塞隊列,提交的任務將會被放到這個隊列裏。

threadFactory:線程工廠,用來建立線程。主要是爲了給線程起名字,默認工廠的線程名字:pool-1-thread-3。

handler:拒絕策略,即當線程和隊列都已經滿了的時候,應該採起什麼樣的策略來處理新提交的任務。默認策略是AbortPolicy(拋出異常),其餘的策略還有:CallerRunsPolicy(只用調用者所在線程來運行任務)、DiscardOldestPolicy(丟棄隊列裏最近的一個任務,並執行當前任務)、DiscardPolicy(不處理,丟棄掉)

19.線程池執行流程?

  任務被提交到線程池,會先判斷當前線程數量是否小於corePoolSize,若是小於則建立線程來執行提交的任務,不然將任務放入workQueue隊列,若是workQueue滿了,則判斷當前線程數量是否小於maximumPoolSize,若是小於則建立線程執行任務,不然就會調用handler,以表示線程池拒絕接收任務。

相關文章
相關標籤/搜索