| 線程是操作系統(tǒng)能夠運行調(diào)度的最小單位,它被包含在進程之中。 | |
| 線程可以有效地降低程序的開發(fā)和維護等成本,同時提升復(fù)雜應(yīng)用程序的性能。 | |
| 線程可以將大部分的異步工作流轉(zhuǎn)換成串行工作流。 | |
| 線程可以降低代碼的復(fù)雜度,是代碼更容易編寫、閱讀和維護。 | |
| 線程是一把雙刃劍。 | |
| 編寫多線程對開發(fā)人員要求高,線程安全不易控制.線程會遇到難以分析的問題,如:死鎖、饑餓。 | |
| 線程同步和切換會帶來性能的問題。 | |
| 2.線程和進程有什么區(qū)別? | |
| 線程是進程的子集,一個進程可以有很多線程,每條線程并行執(zhí)行任務(wù)。 | |
| 不同的進程使用不同的內(nèi)存空間,而同一進程中的所有的線程都將共享進程的內(nèi)存地址空間。 | |
| 3.如何在java中實現(xiàn)線程? | |
| 三種實現(xiàn) | |
| 1.繼承Thread | |
| 2.實現(xiàn)Runnable接口 | |
| 3.使用Executor創(chuàng)建 | |
| 建議使用Exectutor來實現(xiàn)線程,它為靈活且強大的異步任務(wù)執(zhí)行框架提供了基礎(chǔ),該框架能支持多種不同類型的任務(wù)執(zhí)行策略。 | |
| 它提供了一種標(biāo)準(zhǔn)的方法將任務(wù)的提交過程與執(zhí)行過程解耦開來,并用Runnable來表示任務(wù)。Executor的實現(xiàn)還提供了對生命 | |
| 周期的支持以及統(tǒng)計信息收集、應(yīng)用程序管理機制和性能監(jiān)視等機制。 | |
| 4.Thread類中的start()方法和run()方法有什么區(qū)別? | |
| 調(diào)用start()方法是啟動一個新的線程,然后start()方法內(nèi)部調(diào)用了run()方法。 | |
| run()方法只是一個普通的方法,直接調(diào)用run()不會啟動一個新的線程。 | |
| 5.Runnable和Callable有什么不同? | |
| Runnable和Callable都代表那些要在不同的線程中執(zhí)行的任務(wù)。 | |
| 主要區(qū)別在于:Callable()的call()方法可以返回裝載有計算結(jié)果的Future對象和拋出異常,而Runnable不可以。 | |
| 6.volatile變量 | |
| Java語言提供了一種稍弱的同步機制,即 volatile 變量.用來確保將變量的更新操作通知到其他線程,保證了新值能立即同步到主內(nèi)存,以及每次使用前立即從主內(nèi)存刷新. | |
| 當(dāng)把變量聲明為volatile類型后,編譯器與運行時都會注意到這個變量是共享的.提供了線程的可見性。(想象成get()和set()) | |
| volatile只能確保可見性,不能確保原子性。 | |
| 通常用在某個操作完成、發(fā)生中斷或者狀態(tài)的標(biāo)志。(個人認(rèn)為常作用在 boolean 枚舉 常量) | |
| 7.線程安全 | |
| 當(dāng)多個線程訪問某個類時,不管這些程序?qū)⑷绾谓惶鎴?zhí)行,都能表現(xiàn)正確的行為,則代表線程安全。 | |
| 要保證線程安全,外修排斥,內(nèi)修可見。 | |
| 8.競態(tài)條件 | |
| 競態(tài)條件稱為“先檢查后執(zhí)行”:首先觀察到某個條件為真,然后根據(jù)這個觀察結(jié)果采用相應(yīng)的動作,但事實上, | |
| 在你觀察到這個結(jié)果以及開始創(chuàng)建文件之間,觀察結(jié)果可能變得無效了,從而導(dǎo)致各種問題。 | |
| 所以需要保證復(fù)合操作的原子性,使用synchronized或原子變量類。 | |
| 9.如何停止線程? | |
| 如果使用Executor創(chuàng)建的線程直接shutdown就好了 | |
| 使用Thread停止線程 | |
| 1. 使用violate boolean變量來標(biāo)識線程是否停止 | |
| 2. 停止線程時,需要調(diào)用停止線程的interrupt()方法,因為線程有可能在wait()或sleep(), 提高停止線程的即時性 | |
| PRivate volatile Thread myThread; | |
| public void stopMyThread() { | |
| Thread tmpThread = myThread; | |
| myThread = null; | |
| if (tmpThread != null) { | |
| tmpThread.interrupt(); | |
| } | |
| } | |
| public void run() { | |
| if (myThread == null) { | |
| return; // stopped before started. | |
| } | |
| // do some more work | |
| } | |
| 10.在java中wait和sleep方法的不同? | |
| java 線程中的sleep和wait有一個共同作用,停止當(dāng)前線程任務(wù)運行,不同之處在于: | |
| wait是Object的方法,sleep是Thread的方法 | |
| 最主要的wait方法進入等待后,會釋放對象鎖。而sleep方法在同步塊內(nèi)等待,不會釋放鎖對象。 | |
| wait方法必須在同步方法或者同步塊中使用,sleep方法可以在很多地方使用。 | |
| sleep必須捕獲異常,wait不需要 | |
| 11.什么是不可變對象,它對寫并發(fā)應(yīng)用有什么幫助? | |
| 不可變的對象指的是一旦創(chuàng)建之后,它的狀態(tài)就不能改變。String類就是個不可變類,它的對象一旦創(chuàng)建之后,值就不能被改變了。 | |
| 不可變對象對于緩存是非常好的選擇,因為你不需要擔(dān)心它的值會被更改。 | |
| 不可變類的另外一個好處是它自身是線程安全的,你不需要考慮多線程環(huán)境下的線程安全問題。 | |
| 要創(chuàng)建不可變類,要實現(xiàn)下面幾個步驟: | |
| 1.將類聲明為final,所以它不能被繼承 | |
| 2.將所有的成員聲明為私有的,這樣就不允許直接訪問這些成員 | |
| 3.對變量不要提供setter方法 | |
| 4.將所有可變的成員聲明為final,這樣只能對它們賦值一次 | |
| 5.通過構(gòu)造器初始化所有成員,進行深拷貝(deep copy) | |
| 6.在getter方法中,不要直接返回對象本身,而是克隆對象,并返回對象的拷貝 | |
| 12.在Java中什么是線程調(diào)度? | |
| JVM調(diào)度的模式有兩種:分時調(diào)度和搶占式調(diào)度。 | |
| 分時調(diào)度是所有線程輪流獲得CPU使用權(quán),并平均分配每個線程占用CPU的時間; | |
| 搶占式調(diào)度是根據(jù)線程的優(yōu)先級別來獲取CPU的使用權(quán)。JVM的線程調(diào)度模式采用了搶占式模式。 | |
| 既然是搶占調(diào)度,那么我們就能通過設(shè)置優(yōu)先級來“有限”的控制線程的運行順序,注意“有限”一次。 | |
| 14.ThreadLocal | |
| ThreadLocal類用來提供線程內(nèi)部的局部變量。 | |
| 這種變量在多線程環(huán)境下訪問(通過get或set方法訪問)時能保證各個線程里的變量相對獨立于其他線程內(nèi)的變量。 | |
| ThreadLocal實例通常來說都是private static類型的,用于關(guān)聯(lián)線程和線程的上下文。 | |
| ThreadLocal對象通常用于防止對可變的單實例變量(Singleton)或全局變量進行共享。 | |
| 15.BlockingQueue | |
| BlockingQueue | |
| 線程安全的隊列集合。具有阻塞功能。 | |
| 提供了可阻塞的put和take方法,以及支持定義的offer和poll方法。 | |
| 簡化了生產(chǎn)者-消費者設(shè)計的實現(xiàn)過程,它支持任意數(shù)量的生產(chǎn)者和消費者。 | |
| 一種常見的生產(chǎn)者-消費者設(shè)計模式就是線程池與工作隊列的組合。 | |
| 以兩個人包餃子為例,兩者的勞動分工也是一種生產(chǎn)者-消費者模式:其中一個人把做好的面皮放在案板上,而另一個人從案板上拿走面皮并包餃子。 | |
| 這個實例中,面板相當(dāng)于阻塞。如果案板上沒有面皮,那么消費者會一直等待,直到有面皮。 | |
| 如果案板上放滿了,那么生產(chǎn)者會停止做面皮,直到盤加上有更多的空間。我們可以將這種類比擴展為多個生產(chǎn)者和多個消費者,每個人只需和案板打交道。 | |
| 人們不需要直到究竟有多少生產(chǎn)者或者消費者,或者誰生產(chǎn)了指定的工作項。 | |
| 16.ConcurrentHashMap | |
| 同步容器類在執(zhí)行每個操作期間都持有一個鎖,在大量工作的HashMap.get或List.contains如果hashcode分布糟糕,就會導(dǎo)致大量同步集中訪問某一處, | |
| 導(dǎo)致其他線程不能訪問該容器。 | |
| ConcurrentHashMap 并不是將每個方法都在同一個鎖上同步并使得每次只有一個線程訪問容器,而是使用一種力度更細的加鎖機制來實現(xiàn)更大程度的共享。 | |
| 這種機制成為分段鎖,在這種機制中,任意數(shù)量的讀取線程都可以并發(fā)地訪問Map,執(zhí)行讀取操作地線程和執(zhí)行寫入操作地線程可以并發(fā)地訪問Map,并且一定數(shù)量地寫入 | |
| 襄城可以并發(fā)地修改Map。 | |
| ConcurrentHashMap帶來地結(jié)果是,在并發(fā)訪問環(huán)境下將實現(xiàn)更高地吞吐量,而在單線程環(huán)境中只損失非常小地性能。 | |
| 分段鎖機制: | |
| 在ConcurrentHashMap的實現(xiàn)中使用了一個包含16個鎖的數(shù)組,每個鎖保護所有散列桶的1/16,其中第N個散列桶有第(N%16)個鎖來保護。 | |
| 假設(shè)散列函數(shù)具有合理的分布性,并且關(guān)鍵字能夠?qū)崿F(xiàn)均勻分布,那么這大約能把對于鎖的請求減少到原來的1/16。 | |
| 正是這項技術(shù)使得ConcurrentHashMap能夠支持多大16個并發(fā)的寫入器。 | |
| 17.如何在兩個線程間共享數(shù)據(jù)? | |
| 多個線程訪問共享對象和數(shù)據(jù)的方式 | |
| 1.如果每個線程執(zhí)行的代碼相同,可以使用同一個Runnable對象,這個Runnable對象中有那個共享數(shù)據(jù),例如,買票系統(tǒng)就可以這么做。 | |
| 2.如果每個線程執(zhí)行的代碼不同,這時候需要用不同的Runnable對象,有如下兩種方式來實現(xiàn)這些Runnable對象之間的數(shù)據(jù)共享: | |
| 3.將共享數(shù)據(jù)封裝在另外一個對象中,然后將這個對象逐一傳遞給各個Runnable對象。每個線程對共享數(shù)據(jù)的操作方法也分配到那個對象身上去完成, | |
| 這樣容易實現(xiàn)針對該數(shù)據(jù)進行的各個操作的互斥和通信。 | |
| 4.將這些Runnable對象作為某一個類中的內(nèi)部類,共享數(shù)據(jù)作為這個外部類中的成員變量,每個線程對共享數(shù)據(jù)的操作方法也分配給外部類,以便實現(xiàn)對共享數(shù)據(jù)進行的各個操作的互斥和通信,作為內(nèi)部類的各個Runnable對象調(diào)用外部類的這些方法。 | |
| 5.上面兩種方式的組合:將共享數(shù)據(jù)封裝在另外一個對象中,每個線程對共享數(shù)據(jù)的操作方法也分配到那個對象身上去完成,對象作為這個外部類中的成員變量或方法中的局部變量,每個線程的Runnable對象作為外部類中的成員內(nèi)部類或局部內(nèi)部類。 | |
| 6.總之,要同步互斥的幾段代碼最好是分別放在幾個獨立的方法中,這些方法再放在同一個類中,這樣比較容易實現(xiàn)它們之間的同步互斥和通信。 | |
| 7.極端且簡單的方式,即在任意一個類中定義一個static的變量,這將被所有線程共享。 | |
| 18.Java中notify 和 notifyAll有什么區(qū)別? | |
| wait導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 方法,或被其他線程中斷。wait只能由持有對像鎖的線程來調(diào)用。 | |
| notify喚醒在此對象監(jiān)視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程(隨機)。 | |
| 直到當(dāng)前的線程放棄此對象上的鎖,才能繼續(xù)執(zhí)行被喚醒的線程。 | |
| 同Wait方法一樣,notify只能由持有對像鎖的線程來調(diào)用.notifyall也一樣,不同的是notifyall會喚配所有在此對象鎖上等待的線程。 | |
| "只能由持有對像鎖的線程來調(diào)用"說明wait方法與notify方法必須在同步塊內(nèi)執(zhí)行,即synchronized(obj)之內(nèi). | |
| 再者synchronized代碼塊內(nèi)沒有鎖是寸步不行的,所以線程要繼續(xù)執(zhí)行必須獲得鎖。相輔相成 | |
| 19.為什么wait, notify 和 notifyAll這些方法不在thread類里面? | |
| 因為這些是關(guān)于鎖的,而鎖是針對對象的,鎖用于線程的同步應(yīng)用,決定當(dāng)前對象的鎖的方法就應(yīng)該在對象中 | |
| 20. Java中interrupted 和 isInterruptedd方法的區(qū)別? | |
| 1.interrupt()是用來設(shè)置中斷狀態(tài)的。返回true說明中斷狀態(tài)被設(shè)置了而不是被清除了。我們調(diào)用sleep、wait等此類可中斷 | |
| (throw InterruptedException)方法時,一旦方法拋出InterruptedException,當(dāng)前調(diào)用該方法的線程的中斷狀態(tài)就會被jvm自動清除了, | |
| 就是說我們調(diào)用該線程的isInterrupted 方法時是返回false。如果你想保持中斷狀態(tài),可以再次調(diào)用interrupt方法設(shè)置中斷狀態(tài)。 | |
| 這樣做的原因是,java的中斷并不是真正的中斷線程,而只設(shè)置標(biāo)志位(中斷位)來通知用戶。如果你捕獲到中斷異常,說明當(dāng)前線程已經(jīng)被中斷,不需要繼續(xù)保持中斷位。 | |
| interrupted是靜態(tài)方法,返回的是當(dāng)前線程的中斷狀態(tài)。例如,如果當(dāng)前線程被中斷(沒有拋出中斷異常,否則中斷狀態(tài)就會被清除),你調(diào)用interrupted方法,第一次會返回true。 | |
| 然后,當(dāng)前線程的中斷狀態(tài)被方法內(nèi)部清除了。第二次調(diào)用時就會返回false。如果你剛開始一直調(diào)用isInterrupted,則會一直返回true,除非中間線程的中斷狀態(tài)被其他操作清除了。 | |
| 21.什么是線程池? 為什么要使用它? | |
| 線程池,是指管理一組工作線程的資源池。線程池是與隊列密切相關(guān)的,其中在工作隊列中保存了所有等待執(zhí)行的任務(wù)??梢詮墓ぷ麝犃兄蝎@取一個任務(wù),執(zhí)行任務(wù), | |
| 返回線程池等待下一個任務(wù)。 | |
| “在線程池中執(zhí)行任務(wù)”比“為每個任務(wù)分配一個線程”優(yōu)勢更多。通過重用現(xiàn)有的線程而不是創(chuàng)建新線程,可以在處理多個請求時分?jǐn)傇诰€程創(chuàng)建和銷毀過程中產(chǎn)生的巨大開銷。 | |
| 另外一個好處時,當(dāng)請求到達時,工作線程通常已經(jīng)存在,因此不會由于等待創(chuàng)建線程而延遲任務(wù)的執(zhí)行,從而提高了響應(yīng)性。通過適當(dāng)調(diào)整線程池的大小,可以創(chuàng)建 | |
| 足夠多的線程以便使處理器保持忙碌狀態(tài),同時還可以防止過多線程和相互競爭資源而使應(yīng)用程序耗盡內(nèi)存失敗。 | |
| 22.如何避免死鎖? | |
| 一般造成死鎖必須同時滿足如下4個條件: | |
| 1,互斥條件:線程使用的資源必須至少有一個是不能共享的; | |
| 2,請求與保持條件:至少有一個線程必須持有一個資源并且正在等待獲取一個當(dāng)前被其它線程持有的資源; | |
| 3,非剝奪條件:分配資源不能從相應(yīng)的線程中被強制剝奪; | |
| 4,循環(huán)等待條件:第一個線程等待其它線程,后者又在等待第一個線程。 | |
| 因為要產(chǎn)生死鎖,這4個條件必須同時滿足,所以要防止死鎖的話,只需要破壞其中一個條件即可。 | |
| 23.Java中活鎖和死鎖有什么區(qū)別? | |
| ⑴活鎖是指當(dāng)若干事務(wù)要對同一數(shù)據(jù)項加鎖時,造成一些事務(wù)的永久等待,得不到控制權(quán)的現(xiàn)象 | |
| ⑵死鎖是指兩個以上事務(wù)集合中的每一個事務(wù)都在等待加鎖當(dāng)前已被另一事物加鎖的數(shù)據(jù)項,造成互相等待的現(xiàn)象。 | |
| 24.Java中synchronized 和 ReentrantLock 有什么不同? | |
| 1、ReentrantLock 擁有Synchronized相同的并發(fā)性和內(nèi)存語義,此外還多了 鎖投票,定時鎖等候和中斷鎖等候 | |
| 線程A和B都要獲取對象O的鎖定,假設(shè)A獲取了對象O鎖,B將等待A釋放對O的鎖定, | |
| 如果使用 synchronized ,如果A不釋放,B將一直等下去,不能被中斷 | |
| 如果 使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時間以后,中斷等待,而干別的事情 | |
| ReentrantLock獲取鎖定與三種方式: | |
| a) lock(), 如果獲取了鎖立即返回,如果別的線程持有鎖,當(dāng)前線程則一直處于休眠狀態(tài),直到獲取鎖 | |
| b) tryLock(), 如果獲取了鎖立即返回true,如果別的線程正持有鎖,立即返回false; | |
| c)tryLock(long timeout,TimeUnit unit), 如果獲取了鎖定立即返回true,如果別的線程正持有鎖,會等待參數(shù)給定的時間,在等待的過程中,如果獲取了鎖定,就返回true,如果等待超時,返回false; | |
| d) lockInterruptibly:如果獲取了鎖定立即返回,如果沒有獲取鎖定,當(dāng)前線程處于休眠狀態(tài),直到或者鎖定,或者當(dāng)前線程被別的線程中斷 | |
| 2、synchronized是在JVM層面上實現(xiàn)的,不但可以通過一些監(jiān)控工具監(jiān)控synchronized的鎖定,而且在代碼執(zhí)行時出現(xiàn)異常,JVM會自動釋放鎖定,但是使用Lock則不行,lock是通過代碼實現(xiàn)的,要保證鎖定一定會被釋放,就必須將unLock()放到finally{}中 | |
| 3、在資源競爭不是很激烈的情況下,Synchronized的性能要優(yōu)于ReetrantLock,但是在資源競爭很激烈的情況下,Synchronized的性能會下降幾十倍,但是ReetrantLock的性能能維持常態(tài); | |
| 25. Java中的ReadWriteLock是什么? | |
| 對象的方法中一旦加入synchronized修飾,則任何時刻只能有一個線程訪問synchronized修飾的方法。 | |
| 假設(shè)有個數(shù)據(jù)對象擁有寫方法與讀方法,多線程環(huán)境中要想保證數(shù)據(jù)的安全,需對該對象的讀寫方法都要加入 synchronized同步塊。 | |
| 這樣任何線程在寫入時,其它線程無法讀取與改變數(shù)據(jù);如果有線程在讀取時,其他線程也無法讀取或?qū)懭搿?/td> | |
| 這種方式在寫入操作遠大于讀操作時,問題不大,而當(dāng)讀取遠遠大于寫入時,會造成性能瓶頸,因為此種情況下讀取操作是可以同時進行的, | |
| 而加鎖操作限制了數(shù)據(jù)的并發(fā)讀取。 | |
| ReadWriteLock解決了這個問題,當(dāng)寫操作時,其他線程無法讀取或?qū)懭霐?shù)據(jù),而當(dāng)讀操作時,其它線程無法寫入數(shù)據(jù), | |
| 但卻可以讀取數(shù)據(jù) 。 | |
| 26.volatile 變量和 atomic 變量有什么不同? | |
| volatile變量 | |
| 在Java語言中,volatile變量提供了一種輕量級的同步機制,volatile變量用來確保將變量的更新操作通知到其它線程, | |
| volatile變量不會被緩存到寄存器或者對其它處理器不可見的地方,所以在讀取volatile變量時總會返回最新寫入的值,volatile變量通常用來表示某個狀態(tài)標(biāo)識。 | |
| 原子變量: | |
| 原子變量是“更強大的volatile”變量,從實現(xiàn)來看,每個原子變量類的value屬性都是一個volatile變量, | |
| 所以volatile變量的特性原子變量也有。同時,原子變量提供讀、改、寫的原子操作,更強大,更符合一般并發(fā)場景的需求。 |
新聞熱點
疑難解答