多線程
- java中有幾種方法可以實現一個線程?
1.直接繼承thread類;2.實現runnable接口;
- 如何停止一個正在運行的線程?可以使用正在運行的線程,支持線程中斷,通常是定義一個volatile的狀態變量,在運行線程線程中讀這個變量,其它線程中修改這個變量
- notify()和notifyAll()有什么區別?答:
notify()和notifyAll()都是Object對象用於通知處在等待該對象的線程的方法。
void notify(): 喚醒一個正在等待該對象的線程。
void notifyAll(): 喚醒所有正在等待該對象的線程。
兩者的最大區別在於:
notifyAll使所有原來在該對象上等待被notify的線程統統退出wait的狀態,變成等待該對象上的鎖,一旦該對象被解鎖,他們就會去競爭。
notify他只是選擇一個wait狀態線程進行通知,並使它獲得該對象上的鎖,但不驚動其他同樣在等待被該對象notify的線程們,當第一個線程運行完畢以后釋放對象上的鎖,此時如果該對象沒有再次使用notify語句,即便該對象已經空閑,其他wait狀態等待的線程由於沒有得到該對象的通知,繼續處在wait狀態,直到這個對象發出一個notify
notifyAll,它們等待的是被notify或notifyAll,而不是鎖。 - sleep()和 wait()有什么區別?
a:對於sleep()方法,我們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於Object類中的。
b:sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。
在調用sleep()方法的過程中,線程不會釋放對象鎖。
而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法后本線程才進入對象鎖定池准備
獲取對象鎖進入運行狀態。
- 什么是Daemon線程?它有什么意義? 在Java中有兩類線程:User Thread(用戶線程)、Daemon Thread(守護線程) 。Daemon的作用是為其他線程的運行提供便利服務,比如垃圾回收線程就是一個很稱職的守護者。User和Daemon兩者幾乎沒有區別,唯一的不同之處就在於虛擬機的離開:如果 User Thread已經全部退出運行了,只剩下Daemon Thread存在了,虛擬機也就退出了。
- java如何實現多線程之間的通訊和協作?同步、wait|notify、管道、
鎖
- 什么是可重入鎖(ReentrantLock)? 重入鎖指的是在某一個線程中可以多次獲得同一把鎖,在線程中多次操作有鎖的方法。 答: Java.util.concurrent.lock 中的 Lock 框架是鎖定的一個抽象,它允許把鎖定的實現作為 Java 類,而不是作為語言的特性來實現。這就為 Lock 的多種實現留下了空間,各種實現可能有不同的調度算法、性能特性或者鎖定語義。 ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的並發性和內存語義,但是添加了類似鎖投票、定時鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用情況下更佳的性能。(換句話說,當許多線程都想訪問共享資源時,JVM 可以花更少的時候來調度線程,把更多時間用在執行線程上。)
reentrant 鎖意味着什么呢?簡單來說,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那么獲取計數器就加1,然后鎖需要被釋放兩次才能獲得真正釋放。這模仿了 synchronized 的語義;如果線程進入由線程已經擁有的監控器保護的 synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者后續)synchronized 塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個 synchronized 塊時,才釋放鎖。ReentrantLock 相對於固有鎖synchronized,同樣是可重入的,在某些vm版本上提供了比固有鎖更高的性能,提供了更豐富的鎖特性,比如可中斷的鎖,可等待的鎖,平等鎖以及非塊結構的加鎖。從代碼上盡量用固有鎖,vm會對固有鎖做一定的優化,並且代碼可維護和穩定。只有在需要ReentrantLock的一些特性時,可以考慮用ReentrantLock實現。
ReentrantLock 和synchronized比較。來自《java並發編程實戰》
1.為什么JUC框架出現LOCK?
ReentrantLock並不是替代synchronized的方法,而是當內置鎖不適用時,作為一種可選的高級功能。
2.那么Synchronized有哪些缺點?
①. 只有一個condition與鎖相關聯,這個condition是什么?就是synchronized對針對的對象鎖。
②. synchronized無法中斷一個正在等待獲得鎖的線程,也即多線程競爭一個鎖時,其余未得到鎖的線程只能不停的嘗試獲得鎖,而不能中斷。這種情況對於大量的競爭線程會造成性能的下降等后果。
3.我們面對ReentrantLock和synchronized改如何選擇?
Synchronized相比Lock,為許多開發人員所熟悉,並且簡潔緊湊,如果現有程序已經使用了內置鎖,那么盡量保持代碼風格統一,盡量不引入Lock,避免兩種機制混用,容易令人困惑,也容易發生錯誤。
在Synchronized無法滿足需求的情況下,Lock可以作為一種高級工具,這些功能包括“可定時的、可輪詢的與可中斷的鎖獲取操作,公平隊列,以及非塊結構的鎖”否則還是優先使用Synchronized。
最后,未來更可能提升Synchronized而不是Lock的性能,因為Synchronized是JVM的內置屬性,他能執行一些優化,例如對線程封閉的鎖對象的鎖消除優化,通過增加鎖的粒度來消除內置鎖的同步,而如果基於類庫的鎖來實現這些功能,則可能性不大。
- 當一個線程進入某個對象的一個synchronized的實例方法后,其它線程是否可進入此對象的其它方法?
- synchronized和java.util.concurrent.locks.Lock的異同? 答:Lock 和 synchronized 有一點明顯的區別 —— lock 必須在 finally 塊中釋放。否則,如果受保護的代碼將拋出異常,鎖就有可能永遠得不到釋放!這一點區別看起來可能沒什么,但是實際上,它極為重要。忘記在 finally 塊中釋放鎖,可能會在程序中留下一個定時炸彈,當有一天炸彈爆炸時,您要花費很大力氣才有找到源頭在哪。而使用同步,JVM 將確保鎖會獲得自動釋放。 一個 Lock 對象和一個 synchronized 代碼塊之間的主要不同點是:
- synchronized 代碼塊不能夠保證進入訪問等待的線程的先后順序。
- 你不能夠傳遞任何參數給一個 synchronized 代碼塊的入口。因此,對於 synchronized 代碼塊的訪問等待設置超時時間是不可能的事情。
- synchronized 塊必須被完整地包含在單個方法里。而一個 Lock 對象可以把它的 lock() 和 unlock() 方法的調用放在不同的方法里。
- 樂觀鎖和悲觀鎖的理解及如何實現,有哪些實現方式?
樂觀鎖,每次操作時不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,直到成功為止
悲觀鎖是會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。
樂觀鎖可以使用volatile+CAS原語實現,帶參數版本來避免ABA問題,在讀取和替換的時候進行判定版本是否一致
悲觀鎖可以使用synchronize的以及Lock
並發框架
- SynchronizedMap和ConcurrentHashMap(效率高)有什么區別? 答:java5中新增了ConcurrentMap接口和它的一個實現類ConcurrentHashMap。ConcurrentHashMap提供了和Hashtable以及SynchronizedMap中所不同的鎖機制。比起synchronizedMap來,它提供了好得多的並發性。多個讀操作幾乎總可以並發地執行,同時進行的讀和寫操作通常也能並發地執行,而同時進行的寫操作仍然可以不時地並發進行(相關的類也提供了類似的多個讀線程的並發性,但是,只允許有一個活動的寫線程)。Hashtable中采用的鎖機制是一次鎖住整個hash表,從而同一時刻只能由一個線程對其進行操作;而ConcurrentHashMap中則是一次鎖住一個桶。ConcurrentHashMap默認將hash表分為16個桶,諸如get,put,remove等常用操作只鎖當前需要用到的桶。這樣,原來只能一個線程進入,現在卻能同時有16個寫線程執行,並發性能的提升是顯而易見的。前面說到的16個線程指的是寫線程,而讀操作大部分時候都不需要用到鎖。只有在size等操作時才需要鎖住整個hash表。
在迭代方面,ConcurrentHashMap使用了一種不同的迭代方式。在這種迭代方式中,當iterator被創建后集合再發生改變就不再是拋出ConcurrentModificationException,取而代之的是在改變時new新的數據從而不影響原有的數據 ,iterator完成后再將頭指針替換為新的數據 ,這樣iterator線程可以使用原來老的數據,而寫線程也可以並發的完成改變。
ConcurrentHashMap把實際map划分成若干部分來實現它的可擴展性和線程安全。這種划分是使用並發度獲得的,它是ConcurrentHashMap類構造函數的一個可選參數,默認值為16,這樣在多線程情況下就能避免爭用。另外一個不同點是,在被遍歷的時候,即使是 ConcurrentHashMap 被改動,它也不會拋 ConcurrentModificationException。盡管 Iterator 的設計不是為多個線程的同時使用。 - CopyOnWriteArrayList可以用於什么應用場景? 多讀少寫的場景。讀寫並不是在同一個對象上。在寫時會大面積復制數組,所以寫的性能差,在寫完成后將讀的引用改為執行寫的對象。CopyOnWriteArrayList(免鎖容器)的好處之一是當多個迭代器同時遍歷和修改這個列表時,不會拋出ConcurrentModificationException。在CopyOnWriteArrayList中,寫入將導致創建整個底層數組的副本,而源數組將保留在原地,使得復制的數組在被修改時,讀取操作可以安全地執行。
-
如何讓一段程序並發的執行,並最終匯總結果?
答:使用CyclicBarrier 和CountDownLatch都可以,使用CyclicBarrier 在多個關口處將多個線程執行結果匯總,CountDownLatch 在各線程執行完畢后向總線程匯報結果。 -
如何合理的配置java線程池?如CPU密集型的任務,基本線程池應該配置多大?IO密集型的任務,基本線程池應該配置多大?用有界隊列好還是無界隊列好?任務非常多的時候,使用什么阻塞隊列能獲取最好的吞吐量?
答:1)配置線程池時CPU密集型任務可以少配置線程數,大概和機器的cpu核數相當,可以使得每個線程都在執行任務
2)IO密集型時,大部分線程都阻塞,故需要多配置線程數,2*cpu核數
3)有界隊列和無界隊列的配置需區分業務場景,一般情況下配置有界隊列,在一些可能會有爆發性增長的情況下使用無界隊列。
4)任務非常多時,使用非阻塞隊列使用CAS操作替代鎖可以獲得好的吞吐量。
線程安全
- 什么叫線程安全?servlet是線程安全嗎? 答:如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。 或者說:一個類或者程序所提供的接口對於線程來說是原子操作或者多個線程之間的切換不會導致該接口的執行結果存在二義性,也就是說我們不用考慮同步的問題。
servlet不是線程安全的,每個servlet都只被實例化一次,每個調用都是servlet的同一個實例,並且對類變量沒有線程安全,數據量大的時候容易照成異常。 - 同步有幾種實現方法?
同步的實現方法有五種:1.同步方法;2.同步代碼塊;3.使用特殊域變量(volatile)實現線程同步;4.使用重入鎖實現線程同步;5.使用局部變量實現線程同步 。
同步的實現方面有兩種,分別是synchronized,wait與notify wait():使一個線程處於等待狀態,並且釋放所持有的對象的lock。
sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要捕捉InterruptedException異常。
notify():喚醒一個處於等待狀態的線程,注意的是在調用此方法的時候,並不能確切的喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且不是按優先級。
notifyAll():喚醒所有處入等待狀態的線程。 - notify和notifyall的區別? 答:1)notify()和notifyAll()都是Object對象用於通知處在等待該對象的線程的方法。
2)void notify(): 喚醒一個正在等待該對象的線程。
3)void notifyAll(): 喚醒所有正在等待該對象的線程。
兩者的最大區別在於:
notifyAll使所有原來在該對象上等待被notify的線程統統退出wait的狀態,變成等待該對象上的鎖,一旦該對象被解鎖,他們就會去競爭。
notify他只是選擇一個wait狀態線程進行通知,並使它獲得該對象上的鎖,但不驚動其他同樣在等待被該對象notify的線程們,當第一個線程運 行完畢以后釋放對象上的鎖,此時如果該對象沒有再次使用notify語句,即便該對象已經空閑,其他wait狀態等待的線程由於沒有得到該對象的 通知,繼續處在wait狀態,直到這個對象發出一個notify或notifyAll,它們等待的是被notify或notifyAll,而不是鎖。 - sleep()和 wait()有什么區別?
答:sleep是線程類(Thread)的方法,導致此線程暫停執行指定時間,給執行機會給其他線程,但是監控狀態依然保持,到時后會自動恢復。調用sleep不會釋放對象鎖。
wait是Object類的方法,對此對象調用wait方法導致本線程放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象發出notify方法(或notifyAll)后本線程才進入對象鎖定池准備獲得對象鎖進入運行狀態。 - volatile有什么用?能否用一句話說明下volatile的應用場景? 答:Volatile 變量具有 synchronized 的可見性特性,但是不具備原子特性。可以被看作是一種 “程度較輕的 synchronized”;與 synchronized 塊相比,volatile 變量所需的編碼較少,並且運行時開銷也較少,但是它所能實現的功能也僅是 synchronized 的一部分。
您只能在有限的一些情形下使用 volatile 變量替代鎖。要使 volatile 變量提供理想的線程安全,必須同時滿足下面兩個條件:
A. 對變量的寫操作不依賴於當前值。
B. 該變量沒有包含在具有其他變量的不變式中。首先總結一下Volatile的特性:可見性,但不互斥.怎么理解這句話,首先可見性的原因是以為,這個關鍵字可以讓變量不緩存在寄存器里面,每次取值都是直接從主存里面獲取,所以每次都是最新的值.但是不互斥是因為沒有鎖,這里有個改變值的流程(讀取-修改-寫入),這是一個比讀更耗時的一個操作,在沒有加鎖的情況下別的線程讀取這個值可能是任何一個時刻的值;所以根據這個特性可以推導出使用Volatile在少寫多讀的情況下,性能非常好,當然首先要保證不會是多線程同時寫.
Volatile有五個使用場景(參考Brian Goetz (brian.goetz@sun.com)的文章):
1.作為狀態標志
2.一次性安全發布
3.獨立觀察
4.volatile bean模式
5.開銷較低的讀寫鎖策略
- 請說明下java的內存模型及其工作流程。 答:Java把內存划分成兩種:一種是棧內存,一種是堆內存。
棧內存:存放對象:函數中基本類型的變量和對象的引用變量、靜態類方法 ;特點:棧有一個很重要的特殊性,就是存在棧中的數據可以共享。
堆內存:存放對象:用來存放由new創建的對象和數組;特點:在堆中分配的內存,由Java虛擬機的自動垃圾回收器來管理。
java 內存模型 ( java memory model ):根據Java Language Specification中的說明, jvm系統中存在一個主內存(Main Memory或Java Heap Memory),Java中所有對象成員變量都儲存在主存中,對於所有線程都是共享的。每條線程都有自己的工作內存(Working Memory),工作內存中保存的是主存中某些對象成員變量的拷貝,線程對所有對象成員變量的操作都是在工作內存中進行,線程之間無法相互直接訪問,變量傳遞均需要通過主存完成。
(1) 獲取對象監視器的鎖(lock)
(2) 清空工作內存數據, 從主存復制對象成員變量到當前工作內存, 即同步數據 (read and load)
(3) 執行代碼,改變共享變量值 (use and assign)
(4) 將工作內存數據刷回主存 (store and write)
(5) 釋放對象監視器的鎖 (unlock) - 為什么代碼會重排序?
-
Java線程池中submit() 和 execute()方法有什么區別?
兩個方法都可以向線程池提交任務,execute()方法的返回類型是void,它定義在Executor接口中, 而submit()方法可以返回持有計算結果的Future對象,它定義在ExecutorService接口中,它擴展了Executor接口,其它線程池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法。
-
thread類中的yield方法有什么作用?
Yield方法可以暫停當前正在執行的線程對象,讓其它有相同優先級的線程執行。它是一個靜態方法而且只保證當前線程放棄CPU占用而不能保證使其它線程一定能占用CPU,執行yield()的線程有可能在進入到暫停狀態后馬上又被執行。
-
.什么場景下可以使用volatile替換synchronized?
只需要保證共享資源的可見性的時候可以使用volatile替代,synchronized保證可操作的原子性一致性和可見性。 volatile適用於新值不依賴於就值的情形