摘要:下面是線程相關(guān)的熱門面試題,你可以用它來(lái)好好準(zhǔn)備面試。線程安全問(wèn)題都是由全局變量及靜態(tài)變量引起的。持有自旋鎖的線程在之前應(yīng)該釋放自旋鎖以便其它線程可以獲得自旋鎖。
最近看到網(wǎng)上流傳著,各種面試經(jīng)驗(yàn)及面試題,往往都是一大堆技術(shù)題目貼上去,而沒(méi)有答案。
不管你是新程序員還是老手,你一定在面試中遇到過(guò)有關(guān)線程的問(wèn)題。Java語(yǔ)言一個(gè)重要的特點(diǎn)就是內(nèi)置了對(duì)并發(fā)的支持,讓Java大受企業(yè)和程序員的歡迎。大多數(shù)待遇豐厚的Java開(kāi)發(fā)職位都要求開(kāi)發(fā)者精通多線程技術(shù)并且有豐富的Java程序開(kāi)發(fā)、調(diào)試、優(yōu)化經(jīng)驗(yàn),所以線程相關(guān)的問(wèn)題在面試中經(jīng)常會(huì)被提到。
在典型的Java面試中, 面試官會(huì)從線程的基本概念問(wèn)起
如:為什么你需要使用線程, 如何創(chuàng)建線程,用什么方式創(chuàng)建線程比較好(比如:繼承thread類還是調(diào)用Runnable接口),然后逐漸問(wèn)到并發(fā)問(wèn)題像在Java并發(fā)編程的過(guò)程中遇到了什么挑戰(zhàn),Java內(nèi)存模型,JDK1.5引入了哪些更高階的并發(fā)工具,并發(fā)編程常用的設(shè)計(jì)模式,經(jīng)典多線程問(wèn)題如生產(chǎn)者消費(fèi)者,哲學(xué)家就餐,讀寫器或者簡(jiǎn)單的有界緩沖區(qū)問(wèn)題。僅僅知道線程的基本概念是遠(yuǎn)遠(yuǎn)不夠的, 你必須知道如何處理死鎖,競(jìng)態(tài)條件,內(nèi)存沖突和線程安全等并發(fā)問(wèn)題。掌握了這些技巧,你就可以輕松應(yīng)對(duì)多線程和并發(fā)面試了。
許多Java程序員在面試前才會(huì)去看面試題,這很正常。
因?yàn)槭占嬖囶}和練習(xí)很花時(shí)間,所以我從許多面試者那里收集了Java多線程和并發(fā)相關(guān)的50個(gè)熱門問(wèn)題。
下面是Java線程相關(guān)的熱門面試題,你可以用它來(lái)好好準(zhǔn)備面試。
什么是線程?
什么是線程安全和線程不安全?
什么是自旋鎖?
什么是Java內(nèi)存模型?
什么是CAS?
什么是樂(lè)觀鎖和悲觀鎖?
什么是AQS?
什么是原子操作?在Java Concurrency API中有哪些原子類(atomic classes)?
什么是Executors框架?
什么是阻塞隊(duì)列?如何使用阻塞隊(duì)列來(lái)實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模型?
什么是Callable和Future?
什么是FutureTask?
什么是同步容器和并發(fā)容器的實(shí)現(xiàn)?
什么是多線程??jī)?yōu)缺點(diǎn)?
什么是多線程的上下文切換?
ThreadLocal的設(shè)計(jì)理念與作用?
ThreadPool(線程池)用法與優(yōu)勢(shì)?
Concurrent包里的其他東西:ArrayBlockingQueue、CountDownLatch等等。
synchronized和ReentrantLock的區(qū)別?
Semaphore有什么作用?
Java Concurrency API中的Lock接口(Lock interface)是什么?對(duì)比同步它有什么優(yōu)勢(shì)?
Hashtable的size()方法中明明只有一條語(yǔ)句”return count”,為什么還要做同步?
ConcurrentHashMap的并發(fā)度是什么?
ReentrantReadWriteLock讀寫鎖的使用?
CyclicBarrier和CountDownLatch的用法及區(qū)別?
LockSupport工具?
Condition接口及其實(shí)現(xiàn)原理?
Fork/Join框架的理解?
wait()和sleep()的區(qū)別?
線程的五個(gè)狀態(tài)(五種狀態(tài),創(chuàng)建、就緒、運(yùn)行、阻塞和死亡)?
start()方法和run()方法的區(qū)別?
Runnable接口和Callable接口的區(qū)別?
volatile關(guān)鍵字的作用?
Java中如何獲取到線程dump文件?
線程和進(jìn)程有什么區(qū)別?
線程實(shí)現(xiàn)的方式有幾種(四種)?
高并發(fā)、任務(wù)執(zhí)行時(shí)間短的業(yè)務(wù)怎樣使用線程池?并發(fā)不高、任務(wù)執(zhí)行時(shí)間長(zhǎng)的業(yè)務(wù)怎樣使用線程池?并發(fā)高、業(yè)務(wù)執(zhí)行時(shí)間長(zhǎng)的業(yè)務(wù)怎樣使用線程池?
如果你提交任務(wù)時(shí),線程池隊(duì)列已滿,這時(shí)會(huì)發(fā)生什么?
鎖的等級(jí):方法鎖、對(duì)象鎖、類鎖?
如果同步塊內(nèi)的線程拋出異常會(huì)發(fā)生什么?
并發(fā)編程(concurrency)并行編程(parallellism)有什么區(qū)別?
如何保證多線程下 i++ 結(jié)果正確?
一個(gè)線程如果出現(xiàn)了運(yùn)行時(shí)異常會(huì)怎么樣?
如何在兩個(gè)線程之間共享數(shù)據(jù)?
生產(chǎn)者消費(fèi)者模型的作用是什么?
怎么喚醒一個(gè)阻塞的線程?
Java中用到的線程調(diào)度算法是什么
單例模式的線程安全性?
線程類的構(gòu)造方法、靜態(tài)塊是被哪個(gè)線程調(diào)用的?
同步方法和同步塊,哪個(gè)是更好的選擇?
如何檢測(cè)死鎖?怎么預(yù)防死鎖?
什么是線程?線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位,它被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位,可以使用多線程對(duì)進(jìn)行運(yùn)算提速。
比如,如果一個(gè)線程完成一個(gè)任務(wù)要100毫秒,那么用十個(gè)線程完成改任務(wù)只需10毫秒
什么是線程安全和線程不安全?通俗的說(shuō):加鎖的就是是線程安全的,不加鎖的就是是線程不安全的
線程安全線程安全: 就是多線程訪問(wèn)時(shí),采用了加鎖機(jī)制,當(dāng)一個(gè)線程訪問(wèn)該類的某個(gè)數(shù)據(jù)時(shí),進(jìn)行保護(hù),其他線程不能進(jìn)行訪問(wèn),直到該線程讀取完,其他線程才可使用。不會(huì)出現(xiàn)數(shù)據(jù)不一致或者數(shù)據(jù)污染。
一個(gè)線程安全的計(jì)數(shù)器類的同一個(gè)實(shí)例對(duì)象在被多個(gè)線程使用的情況下也不會(huì)出現(xiàn)計(jì)算失誤。很顯然你可以將集合類分成兩組,線程安全和非線程安全的。
Vector 是用同步方法來(lái)實(shí)現(xiàn)線程安全的, 而和它相似的ArrayList不是線程安全的。
線程不安全:就是不提供數(shù)據(jù)訪問(wèn)保護(hù),有可能出現(xiàn)多個(gè)線程先后更改數(shù)據(jù)造成所得到的數(shù)據(jù)是臟數(shù)據(jù)
如果你的代碼所在的進(jìn)程中有多個(gè)線程在同時(shí)運(yùn)行,而這些線程可能會(huì)同時(shí)運(yùn)行這段代碼。如果每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣的,就是線程安全的。
線程安全問(wèn)題都是由全局變量及靜態(tài)變量引起的。
若每個(gè)線程中對(duì)全局變量、靜態(tài)變量只有讀操作,而無(wú)寫操作,一般來(lái)說(shuō),這個(gè)全局變量是線程安全的;若有多個(gè)線程同時(shí)執(zhí)行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。
自旋鎖是SMP架構(gòu)中的一種low-level的同步機(jī)制。
當(dāng)線程A想要獲取一把自選鎖而該鎖又被其它線程鎖持有時(shí),線程A會(huì)在一個(gè)循環(huán)中自選以檢測(cè)鎖是不是已經(jīng)可用了。
自選鎖需要注意:
由于自旋時(shí)不釋放CPU,因而持有自旋鎖的線程應(yīng)該盡快釋放自旋鎖,否則等待該自旋鎖的線程會(huì)一直在那里自旋,這就會(huì)浪費(fèi)CPU時(shí)間。
持有自旋鎖的線程在sleep之前應(yīng)該釋放自旋鎖以便其它線程可以獲得自旋鎖。
實(shí)現(xiàn)自旋鎖參考
https://segmentfault.com/q/1010000000530936
一個(gè)簡(jiǎn)單的while就可以滿足你的要求。
目前的JVM實(shí)現(xiàn)自旋會(huì)消耗CPU,如果長(zhǎng)時(shí)間不調(diào)用doNotify方法,doWait方法會(huì)一直自旋,CPU會(huì)消耗太大。
public class MyWaitNotify3{ MonitorObject myMonitorObject = new MonitorObject(); boolean wasSignalled = false; public void doWait(){ synchronized(myMonitorObject){ while(!wasSignalled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignalled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignalled = true; myMonitorObject.notify(); } } }什么是Java內(nèi)存模型?
Java內(nèi)存模型描述了在多線程代碼中哪些行為是合法的,以及線程如何通過(guò)內(nèi)存進(jìn)行交互。它描述了“程序中的變量“ 和 ”從內(nèi)存或者寄存器獲取或存儲(chǔ)它們的底層細(xì)節(jié)”之間的關(guān)系。Java內(nèi)存模型通過(guò)使用各種各樣的硬件和編譯器的優(yōu)化來(lái)正確實(shí)現(xiàn)以上事情。
Java包含了幾個(gè)語(yǔ)言級(jí)別的關(guān)鍵字,包括:volatile, final以及synchronized,目的是為了幫助程序員向編譯器描述一個(gè)程序的并發(fā)需求。Java內(nèi)存模型定義了volatile和synchronized的行為,更重要的是保證了同步的java程序在所有的處理器架構(gòu)下面都能正確的運(yùn)行。
“一個(gè)線程的寫操作對(duì)其他線程可見(jiàn)”這個(gè)問(wèn)題是因?yàn)榫幾g器對(duì)代碼進(jìn)行重排序?qū)е碌?。例如,只要代碼移動(dòng)不會(huì)改變程序的語(yǔ)義,當(dāng)編譯器認(rèn)為程序中移動(dòng)一個(gè)寫操作到后面會(huì)更有效的時(shí)候,編譯器就會(huì)對(duì)代碼進(jìn)行移動(dòng)。如果編譯器推遲執(zhí)行一個(gè)操作,其他線程可能在這個(gè)操作執(zhí)行完之前都不會(huì)看到該操作的結(jié)果,這反映了緩存的影響。
此外,寫入內(nèi)存的操作能夠被移動(dòng)到程序里更前的時(shí)候。在這種情況下,其他的線程在程序中可能看到一個(gè)比它實(shí)際發(fā)生更早的寫操作。所有的這些靈活性的設(shè)計(jì)是為了通過(guò)給編譯器,運(yùn)行時(shí)或硬件靈活性使其能在最佳順序的情況下來(lái)執(zhí)行操作。在內(nèi)存模型的限定之內(nèi),我們能夠獲取到更高的性能。
看下面代碼展示的一個(gè)簡(jiǎn)單例子:
ClassReordering { int x = 0, y = 0; public void writer() { x = 1; y = 2; } public void reader() { int r1 = y; int r2 = x; } }
讓我們看在兩個(gè)并發(fā)線程中執(zhí)行這段代碼,讀取Y變量將會(huì)得到2這個(gè)值。因?yàn)檫@個(gè)寫入比寫到X變量更晚一些,程序員可能認(rèn)為讀取X變量將肯定會(huì)得到1。但是,寫入操作可能被重排序過(guò)。如果重排序發(fā)生了,那么,就能發(fā)生對(duì)Y變量的寫入操作,讀取兩個(gè)變量的操作緊隨其后,而且寫入到X這個(gè)操作能發(fā)生。程序的結(jié)果可能是r1變量的值是2,但是r2變量的值為0。
但是面試官,有時(shí)候不這么認(rèn)為,認(rèn)為就是JVM內(nèi)存結(jié)構(gòu)JVM內(nèi)存結(jié)構(gòu)主要有三大塊:堆內(nèi)存、方法區(qū)和棧。
堆內(nèi)存是JVM中最大的一塊由年輕代和老年代組成,而年輕代內(nèi)存又被分成三部分,Eden空間、From Survivor空間、To Survivor空間,默認(rèn)情況下年輕代按照8:1:1的比例來(lái)分配;方法區(qū)存儲(chǔ)類信息、常量、靜態(tài)變量等數(shù)據(jù),是線程共享的區(qū)域,為與Java堆區(qū)分,方法區(qū)還有一個(gè)別名Non-Heap(非堆);棧又分為java虛擬機(jī)棧和本地方法棧主要用于方法的執(zhí)行。
JAVA的JVM的內(nèi)存可分為3個(gè)區(qū):堆(heap)、棧(stack)和方法區(qū)(method)
java堆(Java Heap)可通過(guò)參數(shù) -Xms 和-Xmx設(shè)置
Java堆是被所有線程共享,是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊 Java堆在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建。
Java堆唯一的目的是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例和數(shù)組都在這里。
Java堆為了便于更好的回收和分配內(nèi)存,可以細(xì)分為:新生代和老年代;再細(xì)致一點(diǎn)的有Eden空間、From Survivor空間、To Survivor區(qū)。
新生代:包括Eden區(qū)、From Survivor區(qū)、To Survivor區(qū),系統(tǒng)默認(rèn)大小Eden:Survivor=8:1。
老年代:在年輕代中經(jīng)歷了N次垃圾回收后仍然存活的對(duì)象,就會(huì)被放到年老代中。因此,可以認(rèn)為年老代中存放的都是一些生命周期較長(zhǎng)的對(duì)象。
Survivor空間等Java堆可以處在物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可(就像我們的磁盤空間一樣。在實(shí)現(xiàn)時(shí),既可以實(shí)現(xiàn)成固定大小的,也可以是可擴(kuò)展的)。
據(jù)Java虛擬機(jī)規(guī)范的規(guī)定,當(dāng)方法區(qū)無(wú)法滿足內(nèi)存分配需求時(shí),將拋出OutOfMemoryError異常。
java虛擬機(jī)棧(stack)可通過(guò)參數(shù) 棧幀是方法運(yùn)行期的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)棧容量可由-Xss設(shè)置
1.Java虛擬機(jī)棧是線程私有的,它的生命周期與線程相同。
每一個(gè)方法被調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程。
虛擬機(jī)棧是執(zhí)行Java方法的內(nèi)存模型(也就是字節(jié)碼)服務(wù):每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀,用于存儲(chǔ) 局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。
局部變量表:32位變量槽,存放了編譯期可知的各種基本數(shù)據(jù)類型、對(duì)象引用、returnAddress類型。
操作數(shù)棧:基于棧的執(zhí)行引擎,虛擬機(jī)把操作數(shù)棧作為它的工作區(qū),大多數(shù)指令都要從這里彈出數(shù)據(jù)、執(zhí)行運(yùn)算,然后把結(jié)果壓回操作數(shù)棧。
動(dòng)態(tài)連接:每個(gè)棧幀都包含一個(gè)指向運(yùn)行時(shí)常量池(方法區(qū)的一部分)中該棧幀所屬方法的引用。持有這個(gè)引用是為了支持方法調(diào)用過(guò)程中的動(dòng)態(tài)連接。Class文件的常量池中有大量的符號(hào)引用,字節(jié)碼中的方法調(diào)用指令就以常量池中指向方法的符號(hào)引用為參數(shù)。這些符號(hào)引用一部分會(huì)在類加載階段或第一次使用的時(shí)候轉(zhuǎn)化為直接引用,這種轉(zhuǎn)化稱為靜態(tài)解析。另一部分將在每一次的運(yùn)行期間轉(zhuǎn)化為直接應(yīng)用,這部分稱為動(dòng)態(tài)連接
方法出口:返回方法被調(diào)用的位置,恢復(fù)上層方法的局部變量和操作數(shù)棧,如果無(wú)返回值,則把它壓入調(diào)用者的操作數(shù)棧。
局部變量表所需的內(nèi)存空間在編譯期間完成分配,當(dāng)進(jìn)入一個(gè)方法時(shí),這個(gè)方法需要在幀中分配多大的局部變量空間是完全確定的。
在方法運(yùn)行期間不會(huì)改變局部變量表的大小。主要存放了編譯期可知的各種基本數(shù)據(jù)類型、對(duì)象引用 (reference類型)、returnAddress類型)。
java虛擬機(jī)棧,規(guī)定了兩種異常狀況:
如果線程請(qǐng)求的深度大于虛擬機(jī)所允許的深度,將拋出StackOverflowError異常。
如果虛擬機(jī)棧動(dòng)態(tài)擴(kuò)展,而擴(kuò)展時(shí)無(wú)法申請(qǐng)到足夠的內(nèi)存,就會(huì)拋出OutOfMemoryError異常。
本地方法棧可通過(guò)參數(shù) 棧容量可由-Xss設(shè)置
虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法(也就是字節(jié)碼)服務(wù)。
本地方法棧則是為虛擬機(jī)使用到的Native方法服務(wù)。有的虛擬機(jī)(譬如Sun HotSpot虛擬機(jī))直接就把本地方法棧和虛擬機(jī)棧合二為一
方法區(qū)(Method Area)可通過(guò)參數(shù)-XX:MaxPermSize設(shè)置
線程共享內(nèi)存區(qū)域,用于儲(chǔ)存已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量,即編譯器編譯后的代碼,方法區(qū)也稱持久代(Permanent Generation)。
雖然Java虛擬機(jī)規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分,但是它卻有一個(gè)別名叫做Non-Heap(非堆),目的應(yīng)該是與Java堆區(qū)分開(kāi)來(lái)。
如何實(shí)現(xiàn)方法區(qū),屬于虛擬機(jī)的實(shí)現(xiàn)細(xì)節(jié),不受虛擬機(jī)規(guī)范約束。
方法區(qū)主要存放java類定義信息,與垃圾回收關(guān)系不大,方法區(qū)可以選擇不實(shí)現(xiàn)垃圾回收,但不是沒(méi)有垃圾回收。
方法區(qū)域的內(nèi)存回收目標(biāo)主要是針對(duì)常量池的回收和對(duì)類型的卸載。
運(yùn)行時(shí)常量池,也是方法區(qū)的一部分,虛擬機(jī)加載Class后把常量池中的數(shù)據(jù)放入運(yùn)行時(shí)常量池。
運(yùn)行時(shí)常量池JDK1.6之前字符串常量池位于方法區(qū)之中。
JDK1.7字符串常量池已經(jīng)被挪到堆之中。
可通過(guò)參數(shù)-XX:PermSize和-XX:MaxPermSize設(shè)置
常量池(Constant Pool):常量池?cái)?shù)據(jù)編譯期被確定,是Class文件中的一部分。存儲(chǔ)了類、方法、接口等中的常量,當(dāng)然也包括字符串常量。
字符串池/字符串常量池(String Pool/String Constant Pool):是常量池中的一部分,存儲(chǔ)編譯期類中產(chǎn)生的字符串類型數(shù)據(jù)。
運(yùn)行時(shí)常量池(Runtime Constant Pool):方法區(qū)的一部分,所有線程共享。虛擬機(jī)加載Class后把常量池中的數(shù)據(jù)放入到運(yùn)行時(shí)常量池。常量池:可以理解為Class文件之中的資源倉(cāng)庫(kù),它是Class文件結(jié)構(gòu)中與其他項(xiàng)目資源關(guān)聯(lián)最多的數(shù)據(jù)類型。
常量池中主要存放兩大類常量:字面量(Literal)和符號(hào)引用(Symbolic Reference)。
字面量:文本字符串、聲明為final的常量值等。
符號(hào)引用:類和接口的完全限定名(Fully Qualified Name)、字段的名稱和描述符(Descriptor)、方法的名稱和描述符。
直接內(nèi)存可通過(guò)-XX:MaxDirectMemorySize指定,如果不指定,則默認(rèn)與Java堆的最大值(-Xmx指定)一樣。
直接內(nèi)存(Direct Memory)并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是Java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域,但是這部分內(nèi)存也被頻繁地使用,而且也可能導(dǎo)致OutOfMemoryError異常出現(xiàn)。
總結(jié)的簡(jiǎn)單一點(diǎn)java堆(Java Heap)
可通過(guò)參數(shù) -Xms 和-Xmx設(shè)置
Java堆是被所有線程共享,是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊 Java堆在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建
Java堆唯一的目的是存放對(duì)象實(shí)例,幾乎所有的對(duì)象實(shí)例和數(shù)組都在這里
Java堆為了便于更好的回收和分配內(nèi)存,可以細(xì)分為:新生代和老年代;再細(xì)致一點(diǎn)的有Eden空間、From Survivor空間、To Survivor區(qū)
新生代:包括Eden區(qū)、From Survivor區(qū)、To Survivor區(qū),系統(tǒng)默認(rèn)大小Eden:Survivor=8:1。
老年代:在年輕代中經(jīng)歷了N次垃圾回收后仍然存活的對(duì)象,就會(huì)被放到年老代中。因此,可以認(rèn)為年老代中存放的都是一些生命周期較長(zhǎng)的對(duì)象。
java虛擬機(jī)棧(stack)
可通過(guò)參數(shù) 棧幀是方法運(yùn)行期的基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)棧容量可由-Xss設(shè)置
Java虛擬機(jī)棧是線程私有的,它的生命周期與線程相同。
每一個(gè)方法被調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程。
虛擬機(jī)棧是執(zhí)行Java方法的內(nèi)存模型(也就是字節(jié)碼)服務(wù):每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀,用于存儲(chǔ) 局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息
方法區(qū)(Method Area)
可通過(guò)參數(shù)-XX:MaxPermSize設(shè)置
線程共享內(nèi)存區(qū)域),用于儲(chǔ)存已被虛擬機(jī)加載的類信息、常量、靜態(tài)變量,即編譯器編譯后的代碼,方法區(qū)也稱持久代(Permanent Generation)。
方法區(qū)主要存放java類定義信息,與垃圾回收關(guān)系不大,方法區(qū)可以選擇不實(shí)現(xiàn)垃圾回收,但不是沒(méi)有垃圾回收。
方法區(qū)域的內(nèi)存回收目標(biāo)主要是針對(duì)常量池的回收和對(duì)類型的卸載。
運(yùn)行時(shí)常量池,也是方法區(qū)的一部分,虛擬機(jī)加載Class后把常量池中的數(shù)據(jù)放入運(yùn)行時(shí)常量池。
什么是CAS?CAS(compare and swap)的縮寫,中文翻譯成比較并交換。
CAS 不通過(guò)JVM,直接利用java本地方 JNI(Java Native Interface為JAVA本地調(diào)用),直接調(diào)用CPU 的cmpxchg(是匯編指令)指令。
利用CPU的CAS指令,同時(shí)借助JNI來(lái)完成Java的非阻塞算法,實(shí)現(xiàn)原子操作。其它原子操作都是利用類似的特性完成的。
整個(gè)java.util.concurrent都是建立在CAS之上的,因此對(duì)于synchronized阻塞算法,J.U.C在性能上有了很大的提升。
CAS是項(xiàng)樂(lè)觀鎖技術(shù),當(dāng)多個(gè)線程嘗試使用CAS同時(shí)更新同一個(gè)變量時(shí),只有其中一個(gè)線程能更新變量的值,而其它線程都失敗,失敗的線程并不會(huì)被掛起,而是被告知這次競(jìng)爭(zhēng)中失敗,并可以再次嘗試。
CAS應(yīng)用CAS有3個(gè)操作數(shù),內(nèi)存值V,舊的預(yù)期值A(chǔ),要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為B,否則什么都不做。
CAS優(yōu)點(diǎn)確保對(duì)內(nèi)存的讀-改-寫操作都是原子操作執(zhí)行
CAS缺點(diǎn)CAS雖然很高效的解決原子操作,但是CAS仍然存在三大問(wèn)題。ABA問(wèn)題,循環(huán)時(shí)間長(zhǎng)開(kāi)銷大和只能保證一個(gè)共享變量的原子操作
總結(jié)使用CAS在線程沖突嚴(yán)重時(shí),會(huì)大幅降低程序性能;CAS只適合于線程沖突較少的情況使用。
synchronized在jdk1.6之后,已經(jīng)改進(jìn)優(yōu)化。synchronized的底層實(shí)現(xiàn)主要依靠Lock-Free的隊(duì)列,基本思路是自旋后阻塞,競(jìng)爭(zhēng)切換后繼續(xù)競(jìng)爭(zhēng)鎖,稍微犧牲了公平性,但獲得了高吞吐量。在線程沖突較少的情況下,可以獲得和CAS類似的性能;而線程沖突嚴(yán)重的情況下,性能遠(yuǎn)高于CAS。
參考
https://blog.52itstyle.com/archives/948/
Java在JDK1.5之前都是靠synchronized關(guān)鍵字保證同步的,這種通過(guò)使用一致的鎖定協(xié)議來(lái)協(xié)調(diào)對(duì)共享狀態(tài)的訪問(wèn),可以確保無(wú)論哪個(gè)線程持有共享變量的鎖,都采用獨(dú)占的方式來(lái)訪問(wèn)這些變量。獨(dú)占鎖其實(shí)就是一種悲觀鎖,所以可以說(shuō)synchronized是悲觀鎖。
樂(lè)觀鎖樂(lè)觀鎖( Optimistic Locking)其實(shí)是一種思想。相對(duì)悲觀鎖而言,樂(lè)觀鎖假設(shè)認(rèn)為數(shù)據(jù)一般情況下不會(huì)造成沖突,所以在數(shù)據(jù)進(jìn)行提交更新的時(shí)候,才會(huì)正式對(duì)數(shù)據(jù)的沖突與否進(jìn)行檢測(cè),如果發(fā)現(xiàn)沖突了,則讓返回用戶錯(cuò)誤的信息,讓用戶決定如何去做。
什么是AQS?AbstractQueuedSynchronizer簡(jiǎn)稱AQS,是一個(gè)用于構(gòu)建鎖和同步容器的框架。事實(shí)上concurrent包內(nèi)許多類都是基于AQS構(gòu)建,例如ReentrantLock,Semaphore,CountDownLatch,ReentrantReadWriteLock,FutureTask等。AQS解決了在實(shí)現(xiàn)同步容器時(shí)設(shè)計(jì)的大量細(xì)節(jié)問(wèn)題。
AQS使用一個(gè)FIFO的隊(duì)列表示排隊(duì)等待鎖的線程,隊(duì)列頭節(jié)點(diǎn)稱作“哨兵節(jié)點(diǎn)”或者“啞節(jié)點(diǎn)”,它不與任何線程關(guān)聯(lián)。其他的節(jié)點(diǎn)與等待線程關(guān)聯(lián),每個(gè)節(jié)點(diǎn)維護(hù)一個(gè)等待狀態(tài)waitStatus。
CAS 原子操作在concurrent包的實(shí)現(xiàn)參考
https://blog.52itstyle.com/archives/948/
由于java的CAS同時(shí)具有 volatile 讀和volatile寫的內(nèi)存語(yǔ)義,因此Java線程之間的通信現(xiàn)在有了下面四種方式:
A線程寫volatile變量,隨后B線程讀這個(gè)volatile變量。
A線程寫volatile變量,隨后B線程用CAS更新這個(gè)volatile變量。
A線程用CAS更新一個(gè)volatile變量,隨后B線程用CAS更新這個(gè)volatile變量。
A線程用CAS更新一個(gè)volatile變量,隨后B線程讀這個(gè)volatile變量。
Java的CAS會(huì)使用現(xiàn)代處理器上提供的高效機(jī)器級(jí)別原子指令,這些原子指令以原子方式對(duì)內(nèi)存執(zhí)行讀-改-寫操作,這是在多處理器中實(shí)現(xiàn)同步的關(guān)鍵(從本質(zhì)上來(lái)說(shuō),能夠支持原子性讀-改-寫指令的計(jì)算機(jī)器,是順序計(jì)算圖靈機(jī)的異步等價(jià)機(jī)器,因此任何現(xiàn)代的多處理器都會(huì)去支持某種能對(duì)內(nèi)存執(zhí)行原子性讀-改-寫操作的原子指令)。同時(shí),volatile變量的讀/寫和CAS可以實(shí)現(xiàn)線程之間的通信。把這些特性整合在一起,就形成了整個(gè)concurrent包得以實(shí)現(xiàn)的基石。如果我們仔細(xì)分析concurrent包的源代碼實(shí)現(xiàn),會(huì)發(fā)現(xiàn)一個(gè)通用化的實(shí)現(xiàn)模式:
首先,聲明共享變量為volatile;
然后,使用CAS的原子條件更新來(lái)實(shí)現(xiàn)線程之間的同步;
同時(shí),配合以volatile的讀/寫和CAS所具有的volatile讀和寫的內(nèi)存語(yǔ)義來(lái)實(shí)現(xiàn)線程之間的通信。
AQS,非阻塞數(shù)據(jù)結(jié)構(gòu)和原子變量類(Java.util.concurrent.atomic包中的類),這些concurrent包中的基礎(chǔ)類都是使用這種模式來(lái)實(shí)現(xiàn)的,而concurrent包中的高層類又是依賴于這些基礎(chǔ)類來(lái)實(shí)現(xiàn)的。從整體來(lái)看,concurrent包的實(shí)現(xiàn)示意圖如下:
AQS沒(méi)有鎖之類的概念,它有個(gè)state變量,是個(gè)int類型,在不同場(chǎng)合有著不同含義。
AQS圍繞state提供兩種基本操作“獲取”和“釋放”,有條雙向隊(duì)列存放阻塞的等待線程,并提供一系列判斷和處理方法,簡(jiǎn)單說(shuō)幾點(diǎn):
state是獨(dú)占的,還是共享的;
state被獲取后,其他線程需要等待;
state被釋放后,喚醒等待線程;
線程等不及時(shí),如何退出等待。
至于線程是否可以獲得state,如何釋放state,就不是AQS關(guān)心的了,要由子類具體實(shí)現(xiàn)。
AQS中還有一個(gè)表示狀態(tài)的字段state,例如ReentrantLocky用它表示線程重入鎖的次數(shù),Semaphore用它表示剩余的許可數(shù)量,F(xiàn)utureTask用它表示任務(wù)的狀態(tài)。對(duì)state變量值的更新都采用CAS操作保證更新操作的原子性。
AbstractQueuedSynchronizer繼承了AbstractOwnableSynchronizer,這個(gè)類只有一個(gè)變量:exclusiveOwnerThread,表示當(dāng)前占用該鎖的線程,并且提供了相應(yīng)的get,set方法。
ReentrantLock實(shí)現(xiàn)原理
https://www.cnblogs.com/maypattis/p/6403682.html
什么是原子操作?在Java Concurrency API中有哪些原子類(atomic classes)?原子操作是指一個(gè)不受其他操作影響的操作任務(wù)單元。原子操作是在多線程環(huán)境下避免數(shù)據(jù)不一致必須的手段。
int++并不是一個(gè)原子操作,所以當(dāng)一個(gè)線程讀取它的值并加1時(shí),另外一個(gè)線程有可能會(huì)讀到之前的值,這就會(huì)引發(fā)錯(cuò)誤。
為了解決這個(gè)問(wèn)題,必須保證增加操作是原子的,在JDK1.5之前我們可以使用同步技術(shù)來(lái)做到這一點(diǎn)。
到JDK1.5,java.util.concurrent.atomic包提供了int和long類型的裝類,它們可以自動(dòng)的保證對(duì)于他們的操作是原子的并且不需要使用同步。
Executor框架同java.util.concurrent.Executor 接口在Java 5中被引入。
Executor框架是一個(gè)根據(jù)一組執(zhí)行策略調(diào)用,調(diào)度,執(zhí)行和控制的異步任務(wù)的框架。
無(wú)限制的創(chuàng)建線程會(huì)引起應(yīng)用程序內(nèi)存溢出。所以創(chuàng)建一個(gè)線程池是個(gè)更好的的解決方案,因?yàn)榭梢韵拗凭€程的數(shù)量并且可以回收再利用這些線程。
利用Executors框架可以非常方便的創(chuàng)建一個(gè)線程池,
Java通過(guò)Executors提供四種線程池,分別為:
newCachedThreadPool創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過(guò)處理需要,可靈活回收空閑線程,若無(wú)可回收,則新建線程。
newFixedThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,可控制線程最大并發(fā)數(shù),超出的線程會(huì)在隊(duì)列中等待。
newScheduledThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,支持定時(shí)及周期性任務(wù)執(zhí)行。
newSingleThreadExecutor 創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來(lái)執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級(jí))執(zhí)行。
JDK7提供了7個(gè)阻塞隊(duì)列。(也屬于并發(fā)容器)
ArrayBlockingQueue :一個(gè)由數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列。
LinkedBlockingQueue :一個(gè)由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列。
PriorityBlockingQueue :一個(gè)支持優(yōu)先級(jí)排序的無(wú)界阻塞隊(duì)列。
DelayQueue:一個(gè)使用優(yōu)先級(jí)隊(duì)列實(shí)現(xiàn)的無(wú)界阻塞隊(duì)列。
SynchronousQueue:一個(gè)不存儲(chǔ)元素的阻塞隊(duì)列。
LinkedTransferQueue:一個(gè)由鏈表結(jié)構(gòu)組成的無(wú)界阻塞隊(duì)列。
LinkedBlockingDeque:一個(gè)由鏈表結(jié)構(gòu)組成的雙向阻塞隊(duì)列。
什么是阻塞隊(duì)列?阻塞隊(duì)列是一個(gè)在隊(duì)列基礎(chǔ)上又支持了兩個(gè)附加操作的隊(duì)列。
2個(gè)附加操作:
支持阻塞的插入方法:隊(duì)列滿時(shí),隊(duì)列會(huì)阻塞插入元素的線程,直到隊(duì)列不滿。
支持阻塞的移除方法:隊(duì)列空時(shí),獲取元素的線程會(huì)等待隊(duì)列變?yōu)榉强铡?/p>
阻塞隊(duì)列的應(yīng)用場(chǎng)景
阻塞隊(duì)列常用于生產(chǎn)者和消費(fèi)者的場(chǎng)景,生產(chǎn)者是向隊(duì)列里添加元素的線程,消費(fèi)者是從隊(duì)列里取元素的線程。簡(jiǎn)而言之,阻塞隊(duì)列是生產(chǎn)者用來(lái)存放元素、消費(fèi)者獲取元素的容器。
幾個(gè)方法在阻塞隊(duì)列不可用的時(shí)候,上述2個(gè)附加操作提供了四種處理方法
方法處理方式 | 拋出異常 | 返回特殊值 | 一直阻塞 | 超時(shí)退出 |
---|---|---|---|---|
插入方法 | add(e) | offer(e) | put(e) | offer(e,time,unit) |
移除方法 | remove() | poll() | take() | poll(time,unit) |
檢查方法 | element() | peek() | 不可用 | 不可用 |
JDK 7 提供了7個(gè)阻塞隊(duì)列,如下
1、ArrayBlockingQueue 數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列。
此隊(duì)列按照先進(jìn)先出(FIFO)的原則對(duì)元素進(jìn)行排序,但是默認(rèn)情況下不保證線程公平的訪問(wèn)隊(duì)列,即如果隊(duì)列滿了,那么被阻塞在外面的線程對(duì)隊(duì)列訪問(wèn)的順序是不能保證線程公平(即先阻塞,先插入)的。
2、LinkedBlockingQueue一個(gè)由鏈表結(jié)構(gòu)組成的有界阻塞隊(duì)列
此隊(duì)列按照先出先進(jìn)的原則對(duì)元素進(jìn)行排序
3、PriorityBlockingQueue支持優(yōu)先級(jí)的無(wú)界阻塞隊(duì)列
4、DelayQueue支持延時(shí)獲取元素的無(wú)界阻塞隊(duì)列,即可以指定多久才能從隊(duì)列中獲取當(dāng)前元素
5、SynchronousQueue不存儲(chǔ)元素的阻塞隊(duì)列,每一個(gè)put必須等待一個(gè)take操作,否則不能繼續(xù)添加元素。并且他支持公平訪問(wèn)隊(duì)列。
6、LinkedTransferQueue由鏈表結(jié)構(gòu)組成的無(wú)界阻塞TransferQueue隊(duì)列。相對(duì)于其他阻塞隊(duì)列,多了tryTransfer和transfer方法
transfer方法
如果當(dāng)前有消費(fèi)者正在等待接收元素(take或者待時(shí)間限制的poll方法),transfer可以把生產(chǎn)者傳入的元素立刻傳給消費(fèi)者。如果沒(méi)有消費(fèi)者等待接收元素,則將元素放在隊(duì)列的tail節(jié)點(diǎn),并等到該元素被消費(fèi)者消費(fèi)了才返回。
tryTransfer方法
用來(lái)試探生產(chǎn)者傳入的元素能否直接傳給消費(fèi)者。,如果沒(méi)有消費(fèi)者在等待,則返回false。和上述方法的區(qū)別是該方法無(wú)論消費(fèi)者是否接收,方法立即返回。而transfer方法是必須等到消費(fèi)者消費(fèi)了才返回。
7、LinkedBlockingDeque鏈表結(jié)構(gòu)的雙向阻塞隊(duì)列,優(yōu)勢(shì)在于多線程入隊(duì)時(shí),減少一半的競(jìng)爭(zhēng)。
如何使用阻塞隊(duì)列來(lái)實(shí)現(xiàn)生產(chǎn)者-消費(fèi)者模型?通知模式實(shí)現(xiàn):所謂通知模式,就是當(dāng)生產(chǎn)者往滿的隊(duì)列里添加元素時(shí)會(huì)阻塞住生產(chǎn)者,當(dāng)消費(fèi)者消費(fèi)了一個(gè)隊(duì)列中的元素后,會(huì)通知生產(chǎn)者當(dāng)前隊(duì)列可用。
使用BlockingQueue解決生產(chǎn)者消費(fèi)者問(wèn)題為什么BlockingQueue適合解決生產(chǎn)者消費(fèi)者問(wèn)題
任何有效的生產(chǎn)者-消費(fèi)者問(wèn)題解決方案都是通過(guò)控制生產(chǎn)者put()方法(生產(chǎn)資源)和消費(fèi)者take()方法(消費(fèi)資源)的調(diào)用來(lái)實(shí)現(xiàn)的,一旦你實(shí)現(xiàn)了對(duì)方法的阻塞控制,那么你將解決該問(wèn)題。
Java通過(guò)BlockingQueue提供了開(kāi)箱即用的支持來(lái)控制這些方法的調(diào)用(一個(gè)線程創(chuàng)建資源,另一個(gè)消費(fèi)資源)。java.util.concurrent包下的BlockingQueue接口是一個(gè)線程安全的可用于存取對(duì)象的隊(duì)列。
BlockingQueue是一種數(shù)據(jù)結(jié)構(gòu),支持一個(gè)線程往里存資源,另一個(gè)線程從里取資源。這正是解決生產(chǎn)者消費(fèi)者問(wèn)題所需要的,那么讓我們開(kāi)始解決該問(wèn)題吧。
生產(chǎn)者
以下代碼用于生產(chǎn)者線程
package io.ymq.example.thread; import java.util.concurrent.BlockingQueue; /** * 描述:生產(chǎn)者 * * @author yanpenglei * @create 2018-03-14 15:52 **/ class Producer implements Runnable { protected BlockingQueue
消費(fèi)者
以下代碼用于消費(fèi)者線程
package io.ymq.example.thread; import java.util.concurrent.BlockingQueue; /** * 描述: 消費(fèi)者 * * @author yanpenglei * @create 2018-03-14 15:54 **/ class Consumer implements Runnable { protected BlockingQueuequeue; Consumer(BlockingQueue theQueue) { this.queue = theQueue; } public void run() { try { while (true) { Object obj = queue.take(); System.out.println("消費(fèi)者 資源 隊(duì)列大小 " + queue.size()); take(obj); } } catch (InterruptedException ex) { System.out.println("消費(fèi)者 中斷"); } } void take(Object obj) { try { Thread.sleep(100); // simulate time passing } catch (InterruptedException ex) { System.out.println("消費(fèi)者 讀 中斷"); } System.out.println("消費(fèi)對(duì)象 " + obj); } }
測(cè)試該解決方案是否運(yùn)行正常
package io.ymq.example.thread; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; /** * 描述: 測(cè)試 * * @author yanpenglei * @create 2018-03-14 15:58 **/ public class ProducerConsumerExample { public static void main(String[] args) throws InterruptedException { int numProducers = 4; int numConsumers = 3; BlockingQueuemyQueue = new LinkedBlockingQueue (5); for (int i = 0; i < numProducers; i++) { new Thread(new Producer(myQueue)).start(); } for (int i = 0; i < numConsumers; i++) { new Thread(new Consumer(myQueue)).start(); } Thread.sleep(1000); System.exit(0); } }
運(yùn)行結(jié)果
生產(chǎn)者資源隊(duì)列大小= 1 生產(chǎn)者資源隊(duì)列大小= 1 消費(fèi)者 資源 隊(duì)列大小 1 生產(chǎn)者資源隊(duì)列大小= 1 消費(fèi)者 資源 隊(duì)列大小 1 消費(fèi)者 資源 隊(duì)列大小 1 生產(chǎn)者資源隊(duì)列大小= 1 生產(chǎn)者資源隊(duì)列大小= 3 消費(fèi)對(duì)象 java.lang.Object@1e1aa52b 生產(chǎn)者資源隊(duì)列大小= 2 生產(chǎn)者資源隊(duì)列大小= 5 消費(fèi)對(duì)象 java.lang.Object@6e740a76 消費(fèi)對(duì)象 java.lang.Object@697853f6 ...... 消費(fèi)對(duì)象 java.lang.Object@41a10cbc 消費(fèi)對(duì)象 java.lang.Object@4963c8d1 消費(fèi)者 資源 隊(duì)列大小 5 生產(chǎn)者資源隊(duì)列大小= 5 生產(chǎn)者資源隊(duì)列大小= 5 消費(fèi)者 資源 隊(duì)列大小 4 消費(fèi)對(duì)象 java.lang.Object@3e49c35d 消費(fèi)者 資源 隊(duì)列大小 4 生產(chǎn)者資源隊(duì)列大小= 5
從輸出結(jié)果中,我們可以發(fā)現(xiàn)隊(duì)列大小永遠(yuǎn)不會(huì)超過(guò)5,消費(fèi)者線程消費(fèi)了生產(chǎn)者生產(chǎn)的資源。
什么是Callable和Future?Callable 和 Future 是比較有趣的一對(duì)組合。當(dāng)我們需要獲取線程的執(zhí)行結(jié)果時(shí),就需要用到它們。Callable用于產(chǎn)生結(jié)果,F(xiàn)uture用于獲取結(jié)果。
Callable接口使用泛型去定義它的返回類型。Executors類提供了一些有用的方法去在線程池中執(zhí)行Callable內(nèi)的任務(wù)。由于Callable任務(wù)是并行的,必須等待它返回的結(jié)果。java.util.concurrent.Future對(duì)象解決了這個(gè)問(wèn)題。
在線程池提交Callable任務(wù)后返回了一個(gè)Future對(duì)象,使用它可以知道Callable任務(wù)的狀態(tài)和得到Callable返回的執(zhí)行結(jié)果。Future提供了get()方法,等待Callable結(jié)束并獲取它的執(zhí)行結(jié)果。
代碼示例
Callable 是一個(gè)接口,它只包含一個(gè)call()方法。Callable是一個(gè)返回結(jié)果并且可能拋出異常的任務(wù)。
為了便于理解,我們可以將Callable比作一個(gè)Runnable接口,而Callable的call()方法則類似于Runnable的run()方法。
public class CallableFutureTest { public static void main(String[] args) throws InterruptedException, ExecutionException { System.out.println("start main thread "); ExecutorService exec = Executors.newFixedThreadPool(2); //新建一個(gè)Callable 任務(wù),并將其提交到一個(gè)ExecutorService. 將返回一個(gè)描述任務(wù)情況的Future. Callablecall = new Callable () { @Override public String call() throws Exception { System.out.println("start new thread "); Thread.sleep(5000); System.out.println("end new thread "); return "我是返回的內(nèi)容"; } }; Future task = exec.submit(call); Thread.sleep(1000); String retn = task.get(); //關(guān)閉線程池 exec.shutdown(); System.out.println(retn + "--end main thread"); } }
控制臺(tái)打印
start main thread start new thread end new thread 我是返回的內(nèi)容--end main thread什么是FutureTask?
FutureTask可用于異步獲取執(zhí)行結(jié)果或取消執(zhí)行任務(wù)的場(chǎng)景。通過(guò)傳入Runnable或者Callable的任務(wù)給FutureTask,直接調(diào)用其run方法或者放入線程池執(zhí)行,之后可以在外部通過(guò)FutureTask的get方法異步獲取執(zhí)行結(jié)果,因此,F(xiàn)utureTask非常適合用于耗時(shí)的計(jì)算,主線程可以在完成自己的任務(wù)后,再去獲取結(jié)果。另外,F(xiàn)utureTask還可以確保即使調(diào)用了多次run方法,它都只會(huì)執(zhí)行一次Runnable或者Callable任務(wù),或者通過(guò)cancel取消FutureTask的執(zhí)行等。
1.執(zhí)行多任務(wù)計(jì)算FutureTask執(zhí)行多任務(wù)計(jì)算的使用場(chǎng)景
利用FutureTask和ExecutorService,可以用多線程的方式提交計(jì)算任務(wù),主線程繼續(xù)執(zhí)行其他任務(wù),當(dāng)主線程需要子線程的計(jì)算結(jié)果時(shí),在異步獲取子線程的執(zhí)行結(jié)果。
import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; public class FutureTaskForMultiCompute { public static void main(String[] args) { FutureTaskForMultiCompute inst = new FutureTaskForMultiCompute(); // 創(chuàng)建任務(wù)集合 List> taskList = new ArrayList >(); // 創(chuàng)建線程池 ExecutorService exec = Executors.newFixedThreadPool(5); for (int i = 0; i < 10; i++) { // 傳入Callable對(duì)象創(chuàng)建FutureTask對(duì)象 FutureTask ft = new FutureTask (inst.new ComputeTask(i, "" + i)); taskList.add(ft); // 提交給線程池執(zhí)行任務(wù),也可以通過(guò)exec.invokeAll(taskList)一次性提交所有任務(wù); exec.submit(ft); } System.out.println("所有計(jì)算任務(wù)提交完畢, 主線程接著干其他事情!"); // 開(kāi)始統(tǒng)計(jì)各計(jì)算線程計(jì)算結(jié)果 Integer totalResult = 0; for (FutureTask ft : taskList) { try { //FutureTask的get方法會(huì)自動(dòng)阻塞,直到獲取計(jì)算結(jié)果為止 totalResult = totalResult + ft.get(); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } // 關(guān)閉線程池 exec.shutdown(); System.out.println("多任務(wù)計(jì)算后的總結(jié)果是:" + totalResult); } private class ComputeTask implements Callable { private Integer result = 0; private String taskName = ""; public ComputeTask(Integer iniResult, String taskName) { result = iniResult; this.taskName = taskName; System.out.println("生成子線程計(jì)算任務(wù): " + taskName); } public String getTaskName() { return this.taskName; } @Override public Integer call() throws Exception { // TODO Auto-generated method stub for (int i = 0; i < 100; i++) { result = +i; } // 休眠5秒鐘,觀察主線程行為,預(yù)期的結(jié)果是主線程會(huì)繼續(xù)執(zhí)行,到要取得FutureTask的結(jié)果是等待直至完成。 Thread.sleep(5000); System.out.println("子線程計(jì)算任務(wù): " + taskName + " 執(zhí)行完成!"); return result; } } }
生成子線程計(jì)算任務(wù): 0 生成子線程計(jì)算任務(wù): 1 生成子線程計(jì)算任務(wù): 2 生成子線程計(jì)算任務(wù): 3 生成子線程計(jì)算任務(wù): 4 生成子線程計(jì)算任務(wù): 5 生成子線程計(jì)算任務(wù): 6 生成子線程計(jì)算任務(wù): 7 生成子線程計(jì)算任務(wù): 8 生成子線程計(jì)算任務(wù): 9 所有計(jì)算任務(wù)提交完畢, 主線程接著干其他事情! 子線程計(jì)算任務(wù): 0 執(zhí)行完成! 子線程計(jì)算任務(wù): 2 執(zhí)行完成! 子線程計(jì)算任務(wù): 3 執(zhí)行完成! 子線程計(jì)算任務(wù): 4 執(zhí)行完成! 子線程計(jì)算任務(wù): 1 執(zhí)行完成! 子線程計(jì)算任務(wù): 8 執(zhí)行完成! 子線程計(jì)算任務(wù): 7 執(zhí)行完成! 子線程計(jì)算任務(wù): 6 執(zhí)行完成! 子線程計(jì)算任務(wù): 9 執(zhí)行完成! 子線程計(jì)算任務(wù): 5 執(zhí)行完成! 多任務(wù)計(jì)算后的總結(jié)果是:9902.高并發(fā)環(huán)境下
FutureTask在高并發(fā)環(huán)境下確保任務(wù)只執(zhí)行一次
在很多高并發(fā)的環(huán)境下,往往我們只需要某些任務(wù)只執(zhí)行一次。這種使用情景FutureTask的特性恰能勝任。舉一個(gè)例子,假設(shè)有一個(gè)帶key的連接池,當(dāng)key存在時(shí),即直接返回key對(duì)應(yīng)的對(duì)象;當(dāng)key不存在時(shí),則創(chuàng)建連接。對(duì)于這樣的應(yīng)用場(chǎng)景,通常采用的方法為使用一個(gè)Map對(duì)象來(lái)存儲(chǔ)key和連接池對(duì)應(yīng)的對(duì)應(yīng)關(guān)系,典型的代碼如下面所示:
private MapconnectionPool = new HashMap (); private ReentrantLock lock = new ReentrantLock(); public Connection getConnection(String key) { try { lock.lock(); if (connectionPool.containsKey(key)) { return connectionPool.get(key); } else { //創(chuàng)建 Connection Connection conn = createConnection(); connectionPool.put(key, conn); return conn; } } finally { lock.unlock(); } } //創(chuàng)建Connection private Connection createConnection() { return null; }
在上面的例子中,我們通過(guò)加鎖確保高并發(fā)環(huán)境下的線程安全,也確保了connection只創(chuàng)建一次,然而確犧牲了性能。改用ConcurrentHash的情況下,幾乎可以避免加鎖的操作,性能大大提高,但是在高并發(fā)的情況下有可能出現(xiàn)Connection被創(chuàng)建多次的現(xiàn)象。這時(shí)最需要解決的問(wèn)題就是當(dāng)key不存在時(shí),創(chuàng)建Connection的動(dòng)作能放在connectionPool之后執(zhí)行,這正是FutureTask發(fā)揮作用的時(shí)機(jī),基于ConcurrentHashMap和FutureTask的改造代碼如下:
private ConcurrentHashMap> connectionPool = new ConcurrentHashMap >(); public Connection getConnection(String key) throws Exception { FutureTask connectionTask = connectionPool.get(key); if (connectionTask != null) { return connectionTask.get(); } else { Callable callable = new Callable () { @Override public Connection call() throws Exception { // TODO Auto-generated method stub return createConnection(); } }; FutureTask newTask = new FutureTask (callable); connectionTask = connectionPool.putIfAbsent(key, newTask); if (connectionTask == null) { connectionTask = newTask; connectionTask.run(); } return connectionTask.get(); } } //創(chuàng)建Connection private Connection createConnection() { return null; }
經(jīng)過(guò)這樣的改造,可以避免由于并發(fā)帶來(lái)的多次創(chuàng)建連接及鎖的出現(xiàn)。
什么是同步容器和并發(fā)容器的實(shí)現(xiàn)? 一、同步容器主要代表有Vector和Hashtable,以及Collections.synchronizedXxx等。
鎖的粒度為當(dāng)前對(duì)象整體。
迭代器是及時(shí)失敗的,即在迭代的過(guò)程中發(fā)現(xiàn)被修改,就會(huì)拋出ConcurrentModificationException。
主要代表有ConcurrentHashMap、CopyOnWriteArrayList、ConcurrentSkipListMap、ConcurrentSkipListSet。
鎖的粒度是分散的、細(xì)粒度的,即讀和寫是使用不同的鎖。
迭代器具有弱一致性,即可以容忍并發(fā)修改,不會(huì)拋出ConcurrentModificationException。
JDK 7 ConcurrentHashMap
采用分離鎖技術(shù),同步容器中,是一個(gè)容器一個(gè)鎖,但在ConcurrentHashMap中,會(huì)將hash表的數(shù)組部分分成若干段,每段維護(hù)一個(gè)鎖,以達(dá)到高效的并發(fā)訪問(wèn);
JDK 8 ConcurrentHashMap
采用分離鎖技術(shù),同步容器中,是一個(gè)容器一個(gè)鎖,但在ConcurrentHashMap中,會(huì)將hash表的數(shù)組部分分成若干段,每段維護(hù)一個(gè)鎖,以達(dá)到高效的并發(fā)訪問(wèn);
三、阻塞隊(duì)列主要代表有LinkedBlockingQueue、ArrayBlockingQueue、PriorityBlockingQueue(Comparable,Comparator)、SynchronousQueue。
提供了可阻塞的put和take方法,以及支持定時(shí)的offer和poll方法。
適用于生產(chǎn)者、消費(fèi)者模式(線程池和工作隊(duì)列-Executor),同時(shí)也是同步容器
主要代表有ArrayDeque和LinkedBlockingDeque。
意義:正如阻塞隊(duì)列適用于生產(chǎn)者消費(fèi)者模式,雙端隊(duì)列同樣適用與另一種模式,即工作密取。在生產(chǎn)者-消費(fèi)者設(shè)計(jì)中,所有消費(fèi)者共享一個(gè)工作隊(duì)列,而在工作密取中,每個(gè)消費(fèi)者都有各自的雙端隊(duì)列。
如果一個(gè)消費(fèi)者完成了自己雙端隊(duì)列中的全部工作,那么他就可以從其他消費(fèi)者的雙端隊(duì)列末尾秘密的獲取工作。具有更好的可伸縮性,這是因?yàn)楣ぷ髡呔€程不會(huì)在單個(gè)共享的任務(wù)隊(duì)列上發(fā)生競(jìng)爭(zhēng)。
在大多數(shù)時(shí)候,他們都只是訪問(wèn)自己的雙端隊(duì)列,從而極大的減少了競(jìng)爭(zhēng)。當(dāng)工作者線程需要訪問(wèn)另一個(gè)隊(duì)列時(shí),它會(huì)從隊(duì)列的尾部而不是頭部獲取工作,因此進(jìn)一步降低了隊(duì)列上的競(jìng)爭(zhēng)。
適用于:網(wǎng)頁(yè)爬蟲(chóng)等任務(wù)中
如果不需要阻塞隊(duì)列,優(yōu)先選擇ConcurrentLinkedQueue;
如果需要阻塞隊(duì)列,隊(duì)列大小固定優(yōu)先選擇ArrayBlockingQueue,隊(duì)列大小不固定優(yōu)先選擇LinkedBlockingQueue;
如果需要對(duì)隊(duì)列進(jìn)行排序,選擇PriorityBlockingQueue;
如果需要一個(gè)快速交換的隊(duì)列,選擇SynchronousQueue;
如果需要對(duì)隊(duì)列中的元素進(jìn)行延時(shí)操作,則選擇DelayQueue。
什么是多線程?
多線程:是指從軟件或者硬件上實(shí)現(xiàn)多個(gè)線程的并發(fā)技術(shù)。
多線程的好處:
使用多線程可以把程序中占據(jù)時(shí)間長(zhǎng)的任務(wù)放到后臺(tái)去處理,如圖片、視屏的下載
發(fā)揮多核處理器的優(yōu)勢(shì),并發(fā)執(zhí)行讓系統(tǒng)運(yùn)行的更快、更流暢,用戶體驗(yàn)更好
多線程的缺點(diǎn):
大量的線程降低代碼的可讀性;
更多的線程需要更多的內(nèi)存空間
當(dāng)多個(gè)線程對(duì)同一個(gè)資源出現(xiàn)爭(zhēng)奪時(shí)候要注意線程安全的問(wèn)題。
什么是多線程的上下文切換?即使是單核CPU也支持多線程執(zhí)行代碼,CPU通過(guò)給每個(gè)線程分配CPU時(shí)間片來(lái)實(shí)現(xiàn)這個(gè)機(jī)制。時(shí)間片是CPU分配給各個(gè)線程的時(shí)間,因?yàn)闀r(shí)間片非常短,所以CPU通過(guò)不停地切換線程執(zhí)行,讓我們感覺(jué)多個(gè)線程時(shí)同時(shí)執(zhí)行的,時(shí)間片一般是幾十毫秒(ms)
上下文切換過(guò)程中,CPU會(huì)停止處理當(dāng)前運(yùn)行的程序,并保存當(dāng)前程序運(yùn)行的具體位置以便之后繼續(xù)運(yùn)行
CPU通過(guò)時(shí)間片分配算法來(lái)循環(huán)執(zhí)行任務(wù),當(dāng)前任務(wù)執(zhí)行一個(gè)時(shí)間片后會(huì)切換到下一個(gè)任務(wù)。但是,在切換前會(huì)保存上一個(gè)任務(wù)的狀態(tài),以便下次切換回這個(gè)任務(wù)時(shí),可以再次加載這個(gè)任務(wù)的狀態(tài)
從任務(wù)保存到再加載的過(guò)程就是一次上下文切換
ThreadLocal的設(shè)計(jì)理念與作用?Java中的ThreadLocal類允許我們創(chuàng)建只能被同一個(gè)線程讀寫的變量。因此,如果一段代碼含有一個(gè)ThreadLocal變量的引用,即使兩個(gè)線程同時(shí)執(zhí)行這段代碼,它們也無(wú)法訪問(wèn)到對(duì)方的ThreadLocal變量
ThreadLocal如何創(chuàng)建ThreadLocal變量
以下代碼展示了如何創(chuàng)建一個(gè)ThreadLocal變量:
private ThreadLocal myThreadLocal = new ThreadLocal();
通過(guò)這段代碼實(shí)例化了一個(gè)ThreadLocal對(duì)象。我們只需要實(shí)例化對(duì)象一次,并且也不需要知道它是被哪個(gè)線程實(shí)例化。雖然所有的線程都能訪問(wèn)到這個(gè)ThreadLocal實(shí)例,但是每個(gè)線程卻只能訪問(wèn)到自己通過(guò)調(diào)用ThreadLocal的set()方法設(shè)置的值。即使是兩個(gè)不同的線程在同一個(gè)ThreadLocal對(duì)象上設(shè)置了不同的值,他們?nèi)匀粺o(wú)法訪問(wèn)到對(duì)方的值。
如何訪問(wèn)ThreadLocal變量
一旦創(chuàng)建了一個(gè)ThreadLocal變量,你可以通過(guò)如下代碼設(shè)置某個(gè)需要保存的值:
myThreadLocal.set("A thread local value”);
可以通過(guò)下面方法讀取保存在ThreadLocal變量中的值:
String threadLocalValue = (String) myThreadLocal.get();
get()方法返回一個(gè)Object對(duì)象,set()對(duì)象需要傳入一個(gè)Object類型的參數(shù)。
為ThreadLocal指定泛型類型
public static ThreadLocalmyThreadLocal = new ThreadLocal ();
我們可以創(chuàng)建一個(gè)指定泛型類型的ThreadLocal對(duì)象,這樣我們就不需要每次對(duì)使用get()方法返回的值作強(qiáng)制類型轉(zhuǎn)換了。下面展示了指定泛型類型的ThreadLocal例子:
ThreadLocal的設(shè)計(jì)理念與作用
http://blog.csdn.net/u0118607...://blog.csdn.net/u011860731/article/details/48733073)
InheritableThreadLocalpublic static ThreadLocalthreadLocal = new InheritableThreadLocal ();
InheritableThreadLocal類是ThreadLocal類的子類。ThreadLocal中每個(gè)線程擁有它自己的值,與ThreadLocal不同的是,InheritableThreadLocal允許一個(gè)線程以及該線程創(chuàng)建的所有子線程都可以訪問(wèn)它保存的值。
InheritableThreadLocal 原理
Java 多線程:InheritableThreadLocal 實(shí)現(xiàn)原理
http://blog.csdn.net/ni357103403/article/details/51970748
ThreadPool(線程池)用法與優(yōu)勢(shì)? 為什么要用線程池:減少了創(chuàng)建和銷毀線程的次數(shù),每個(gè)工作線程都可以被重復(fù)利用,可執(zhí)行多個(gè)任務(wù)。
可以根據(jù)系統(tǒng)的承受能力,調(diào)整線程池中工作線線程的數(shù)目,防止因?yàn)橄倪^(guò)多的內(nèi)存,而把服務(wù)器累趴下(每個(gè)線程需要大約1MB內(nèi)存,線程開(kāi)的越多,消耗的內(nèi)存也就越大,最后死機(jī))。
Java里面線程池的頂級(jí)接口是Executor,但是嚴(yán)格意義上講Executor并不是一個(gè)線程池,而只是一個(gè)執(zhí)行線程的工具。真正的線程池接口是ExecutorService。
new Thread 缺點(diǎn)每次new Thread新建對(duì)象性能差。
線程缺乏統(tǒng)一管理,可能無(wú)限制新建線程,相互之間競(jìng)爭(zhēng),及可能占用過(guò)多系統(tǒng)資源導(dǎo)致死機(jī)或oom。
缺乏更多功能,如定時(shí)執(zhí)行、定期執(zhí)行、線程中斷。
ThreadPool 優(yōu)點(diǎn)減少了創(chuàng)建和銷毀線程的次數(shù),每個(gè)工作線程都可以被重復(fù)利用,可執(zhí)行多個(gè)任務(wù)
可以根據(jù)系統(tǒng)的承受能力,調(diào)整線程池中工作線線程的數(shù)目,防止因?yàn)橐驗(yàn)橄倪^(guò)多的內(nèi)存,而把服務(wù)器累趴下(每個(gè)線程需要大約1MB內(nèi)存,線程開(kāi)的越多,消耗的內(nèi)存也就越大,最后死機(jī))
減少在創(chuàng)建和銷毀線程上所花的時(shí)間以及系統(tǒng)資源的開(kāi)銷
如不使用線程池,有可能造成系統(tǒng)創(chuàng)建大量線程而導(dǎo)致消耗完系統(tǒng)內(nèi)存
Java提供的四種線程池的好處在于:
重用存在的線程,減少對(duì)象創(chuàng)建、銷毀的開(kāi)銷,提高性能。
可有效控制最大并發(fā)線程數(shù),提高系統(tǒng)資源的使用率,同時(shí)避免過(guò)多資源競(jìng)爭(zhēng),避免堵塞。
提供定時(shí)執(zhí)行、定期執(zhí)行、單線程、并發(fā)數(shù)控制等功能。
比較重要的幾個(gè)類:類 | 描述 |
---|---|
ExecutorService | 真正的線程池接口。 |
ScheduledExecutorService | 能和Timer/TimerTask類似,解決那些需要任務(wù)重復(fù)執(zhí)行的問(wèn)題。 |
ThreadPoolExecutor | ExecutorService的默認(rèn)實(shí)現(xiàn)。 |
ScheduledThreadPoolExecutor | 繼承ThreadPoolExecutor的ScheduledExecutorService接口實(shí)現(xiàn),周期性任務(wù)調(diào)度的類實(shí)現(xiàn)。 |
要配置一個(gè)線程池是比較復(fù)雜的,尤其是對(duì)于線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優(yōu)的,因此在Executors類里面提供了一些靜態(tài)工廠,生成一些常用的線程池。
Executors提供四種線程池newCachedThreadPool創(chuàng)建一個(gè)可緩存線程池,如果線程池長(zhǎng)度超過(guò)處理需要,可靈活回收空閑線程,若無(wú)可回收,則新建線程。
newFixedThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,可控制線程最大并發(fā)數(shù),超出的線程會(huì)在隊(duì)列中等待。
newScheduledThreadPool 創(chuàng)建一個(gè)定長(zhǎng)線程池,支持定時(shí)及周期性任務(wù)執(zhí)行。
newSingleThreadExecutor 創(chuàng)建一個(gè)單線程化的線程池,它只會(huì)用唯一的工作線程來(lái)執(zhí)行任務(wù),保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級(jí))執(zhí)行。
一般都不用Executors提供的線程創(chuàng)建方式使用ThreadPoolExecutor創(chuàng)建線程池
ThreadPoolExecutor的構(gòu)造函數(shù)
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue參數(shù):workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { if (corePoolSize < 0 || maximumPoolSize <= 0 || maximumPoolSize < corePoolSize || keepAliveTime < 0) throw new IllegalArgumentException(); if (workQueue == null || threadFactory == null || handler == null) throw new NullPointerException(); this.corePoolSize = corePoolSize; this.maximumPoolSize = maximumPoolSize; this.workQueue = workQueue; this.keepAliveTime = unit.toNanos(keepAliveTime); this.threadFactory = threadFactory; this.handler = handler; }
corePoolSize核心線程數(shù)大小,當(dāng)線程數(shù)
maximumPoolSize 最大線程數(shù), 當(dāng)線程數(shù) >= corePoolSize的時(shí)候,會(huì)把runnable放入workQueue中
keepAliveTime 保持存活時(shí)間,當(dāng)線程數(shù)大于corePoolSize的空閑線程能保持的最大時(shí)間。
unit 時(shí)間單位
workQueue 保存任務(wù)的阻塞隊(duì)列
threadFactory 創(chuàng)建線程的工廠
handler 拒絕策略 當(dāng)線程數(shù)小于corePoolSize時(shí),創(chuàng)建線程執(zhí)行任務(wù)。 當(dāng)線程數(shù)大于等于corePoolSize并且workQueue沒(méi)有滿時(shí),放入workQueue中 線程數(shù)大于等于corePoolSize并且當(dāng)workQueue滿時(shí),新任務(wù)新建線程運(yùn)行,線程總數(shù)要小于maximumPoolSize 當(dāng)線程總數(shù)等于maximumPoolSize并且workQueue滿了的時(shí)候執(zhí)行handler的rejectedExecution。也就是拒絕策略。
ThreadPoolExecutor.AbortPolicy() 直接拋出異常RejectedExecutionException
ThreadPoolExecutor.CallerRunsPolicy() 直接調(diào)用run方法并且阻塞執(zhí)行
ThreadPoolExecutor.DiscardPolicy() 直接丟棄后來(lái)的任務(wù)
ThreadPoolExecutor.DiscardOldestPolicy() 丟棄在隊(duì)列中隊(duì)首的任務(wù) 當(dāng)然可以自己繼承 RejectedExecutionHandler 來(lái)寫拒絕策略. https://juejin.im/post/59df0c1af265da432f301c8d 阻塞隊(duì)列 1、ArrayBlockingQueue 數(shù)組結(jié)構(gòu)組成的有界阻塞隊(duì)列。 此隊(duì)列按照先進(jìn)先出(FIFO)的原則對(duì)元素進(jìn)行排序,但是默認(rèn)情況下不保證線程公平的訪問(wèn)隊(duì)列,即如果隊(duì)列滿了,那么被阻塞在外面的線程對(duì)隊(duì)列訪問(wèn)的順序是不能保證線程公平(即先阻塞,先插入)的。 CountDownLatch 允許一個(gè)或多個(gè)線程等待其他線程完成操作。 應(yīng)用場(chǎng)景 假如有這樣一個(gè)需求,當(dāng)我們需要解析一個(gè)Excel里多個(gè)sheet的數(shù)據(jù)時(shí),可以考慮使用多線程,每個(gè)線程解析一個(gè)sheet里的數(shù)據(jù),等到所有的sheet都解析完之后,程序需要提示解析完成。 在這個(gè)需求中,要實(shí)現(xiàn)主線程等待所有線程完成sheet的解析操作,最簡(jiǎn)單的做法是使用join。代碼如下: join用于讓當(dāng)前執(zhí)行線程等待join線程執(zhí)行結(jié)束。其實(shí)現(xiàn)原理是不停檢查join線程是否存活,如果join線程存活則讓當(dāng)前線程永遠(yuǎn)wait,代碼片段如下,wait(0)表示永遠(yuǎn)等待下去。 方法isAlive()功能是判斷當(dāng)前線程是否處于活動(dòng)狀態(tài)。 活動(dòng)狀態(tài)就是線程啟動(dòng)且尚未終止,比如正在運(yùn)行或準(zhǔn)備開(kāi)始運(yùn)行。 new CountDownLatch(2)的構(gòu)造函數(shù)接收一個(gè)int類型的參數(shù)作為計(jì)數(shù)器,如果你想等待N個(gè)點(diǎn)完成,這里就傳入N。 當(dāng)我們調(diào)用一次CountDownLatch的countDown()方法時(shí),N就會(huì)減1,CountDownLatch的await()會(huì)
public class JoinCountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
Thread parser1 = new Thread(new Runnable() {
@Override
public void run() {
}
});
Thread parser2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("parser2 finish");
}
});
parser1.start();
parser2.start();
parser1.join();
parser2.join();
System.out.println("all parser finish");
}
}
while (isAlive()) {
wait(0);
}
public class Test {
public static void main(String[] args) {
final CountDownLatch latch = new CountDownLatch(2);
new Thread(){
public void run() {
try {
System.out.println("子線程"+Thread.currentThread().getName()+"正在執(zhí)行");
Thread.sleep(3000);
System.out.println("子線程"+Thread.currentThread().getName()+"執(zhí)行完畢");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
new Thread(){
public void run() {
try {
System.out.println("子線程"+Thread.currentThread().getName()+"正在執(zhí)行");
Thread.sleep(3000);
System.out.println("子線程"+Thread.currentThread().getName()+"執(zhí)行完畢");
latch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
}.start();
try {
System.out.println("等待2個(gè)子線程執(zhí)行完畢...");
latch.await();
System.out.println("2個(gè)子線程已經(jīng)執(zhí)行完畢");
System.out.println("繼續(xù)執(zhí)行主線程");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
線程Thread-0正在執(zhí)行
線程Thread-1正在執(zhí)行
等待2個(gè)子線程執(zhí)行完畢...
線程Thread-0執(zhí)行完畢
線程Thread-1執(zhí)行完畢
2個(gè)子線程已經(jīng)執(zhí)行完畢
繼續(xù)執(zhí)行主線程
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/68852.html
摘要:大多數(shù)待遇豐厚的開(kāi)發(fā)職位都要求開(kāi)發(fā)者精通多線程技術(shù)并且有豐富的程序開(kāi)發(fā)調(diào)試優(yōu)化經(jīng)驗(yàn),所以線程相關(guān)的問(wèn)題在面試中經(jīng)常會(huì)被提到。掌握了這些技巧,你就可以輕松應(yīng)對(duì)多線程和并發(fā)面試了。進(jìn)入等待通行準(zhǔn)許時(shí),所提供的對(duì)象。 最近看到網(wǎng)上流傳著,各種面試經(jīng)驗(yàn)及面試題,往往都是一大堆技術(shù)題目貼上去,而沒(méi)有答案。 不管你是新程序員還是老手,你一定在面試中遇到過(guò)有關(guān)線程的問(wèn)題。Java語(yǔ)言一個(gè)重要的特點(diǎn)就...
摘要:為程序員金三銀四精心挑選的余道面試題與答案,歡迎大家向我推薦你在面試過(guò)程中遇到的問(wèn)題我會(huì)把大家推薦的問(wèn)題添加到下面的常用面試題清單中供大家參考。 為Java程序員金三銀四精心挑選的300余道Java面試題與答案,歡迎大家向我推薦你在面試過(guò)程中遇到的問(wèn)題,我會(huì)把大家推薦的問(wèn)題添加到下面的常用面試題清單中供大家參考。 前兩天寫的以下博客,大家比較認(rèn)可,熱度不錯(cuò),希望可以幫到準(zhǔn)備或者正在參加...
摘要:前言從號(hào)開(kāi)始在寫下第一篇文章說(shuō)是筆記還差不多,驚奇地收到有人收藏我的文章的消息,覺(jué)得有點(diǎn)開(kāi)心。突然腦子抽到想爬下里標(biāo)簽下的文章有多少,哪篇被收藏最多,哪篇被點(diǎn)贊最多。?!,F(xiàn)在和大家分享下,收藏量前的文章,被那么多人收藏應(yīng)該是篇值得看的文章。 前言 從18號(hào)開(kāi)始在sf寫下第一篇文章(說(shuō)是筆記還差不多),驚奇地收到有人收藏我的文章的消息,覺(jué)得有點(diǎn)開(kāi)心。突然腦子抽到想爬下sf里JAVA標(biāo)簽下...
閱讀 2137·2023-04-25 15:11
閱讀 3846·2021-09-23 11:57
閱讀 1486·2021-07-26 23:38
閱讀 1414·2019-08-30 15:54
閱讀 698·2019-08-30 15:53
閱讀 3320·2019-08-26 13:36
閱讀 1066·2019-08-26 12:01
閱讀 2948·2019-08-23 16:21