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