1) 什麼是線程?
線程是操做系統可以進行運算調度的最小單位,它被包含在進程之中,是進程中的實際運做單位。程序員能夠經過它進行多處理器編程,你可使用多線程對運算密集型任務提速。好比,若是一個線程完成一個任務要100毫秒,那麼用十個線程完成改任務只需10毫秒。Java在語言層面對多線程提供了卓越的支持,它也是一個很好的賣點。html
2) 線程和進程有什麼區別?
線程是進程的子集,一個進程能夠有不少線程,每條線程並行執行不一樣的任務。不一樣的進程使用不一樣的內存空間,而全部的線程共享一片相同的內存空間。別把它和棧內存搞混,每一個線程都擁有單獨的棧內存用來存儲本地數據。java
3) 如何在Java中實現線程?
1)java.lang.Thread 類的實例就是一個線程可是它須要調用java.lang.Runnable接口來執行,linux
2)因爲線程類自己就是調用的Runnable接口因此你能夠繼承java.lang.Thread 類或者直接調用Runnable接口來重寫run()方法實現線程。程序員
3).實現Callable接口經過FutureTask包裝器來建立Thread線程面試
Callable接口(也只有一個方法)定義以下: 算法
public interface Callable<V> {
V call() throws Exception; }
public class SomeCallable<V> extends OtherClass implements Callable<V> {
@Override
public V call() throws Exception {
// TODO Auto-generated method stub
return null;
}
}
Callable<V> oneCallable = new SomeCallable<V>();
//由Callable<Integer>建立一個FutureTask<Integer>對象:
FutureTask<V> oneTask = new FutureTask<V>(oneCallable);
//註釋:FutureTask<Integer>是一個包裝器,它經過接受Callable<Integer>來建立,它同時實現了Future和Runnable接口。
//由FutureTask<Integer>建立一個Thread對象:
Thread oneThread = new Thread(oneTask);
oneThread.start();
//至此,一個線程就建立完成了。
4).
使用ExecutorService、Callable、Future實現有返回結果的線程
ExecutorService、Callable、Future三個接口實際上都是屬於Executor框架。返回結果的線程是在JDK1.5中引入的新特徵,有了這種特徵就不須要再爲了獲得返回值而大費周折了。並且本身實現了也可能漏洞百出。數據庫
可返回值的任務必須實現Callable接口。相似的,無返回值的任務必須實現Runnable接口。編程
執行Callable任務後,能夠獲取一個Future的對象,在該對象上調用get就能夠獲取到Callable任務返回的Object了。緩存
注意:get方法是阻塞的,即:線程無返回結果,get方法會一直等待。安全
再結合線程池接口ExecutorService就能夠實現傳說中有返回結果的多線程了。
5).Thread 類中的start() 和 run() 方法有什麼區別
1.start()方法來啓動線程,真正實現了多線程運行。這時無需等待run方法體代碼執行完畢,能夠直接繼續執行下面的代碼;經過調用Thread類的start()方法來啓動一個線程, 這時此線程是處於就緒狀態, 並無運行。 而後經過此Thread類調用方法run()來完成其運行操做的, 這裏方法run()稱爲線程體,它包含了要執行的這個線程的內容, Run方法運行結束, 此線程終止。而後CPU再調度其它線程。
2.run()方法看成普通方法的方式調用。程序仍是要順序執行,要等待run方法體執行完畢後,纔可繼續執行下面的代碼; 程序中只有主線程——這一個線程, 其程序執行路徑仍是隻有一條, 這樣就沒有達到寫線程的目的。
記住:多線程就是分時利用CPU,宏觀上讓全部線程一塊兒執行 ,也叫併發
6).Java中CyclicBarrier 和 CountDownLatch有什麼不一樣
CountDownLatch |
CyclicBarrier |
減計數方式 |
加計數方式 |
計算爲0時釋放全部等待的線程 |
計數達到指定值時釋放全部等待線程 |
計數爲0時,沒法重置 |
計數達到指定值時,計數置爲0從新開始 |
調用countDown()方法計數減一,調用await()方法只進行阻塞,對計數沒任何影響 |
調用await()方法計數加1,若加1後的值不等於構造方法的值,則線程阻塞 |
不可重複利用 |
可重複利用 |
7).Java內存模型是什麼?
Java內存模型規定和指引Java程序在不一樣的內存架構、CPU和操做系統間有肯定性地行爲。它在多線程的狀況下尤爲重要。Java內存模型對一個線程所作的變更能被其它線程可見提供了保證,它們之間是先行發生關係。這個關係定義了一些規則讓程序員在併發編程時思路更清晰。好比,先行發生關係確保了:
- 線程內的代碼可以按前後順序執行,這被稱爲程序次序規則。
- 對於同一個鎖,一個解鎖操做必定要發生在時間上後發生的另外一個鎖定操做以前,也叫作管程鎖定規則。
- 前一個對
volatile
的寫操做在後一個volatile
的讀操做以前,也叫volatile
變量規則。
- 一個線程內的任何操做必需在這個線程的start()調用以後,也叫做線程啓動規則。
- 一個線程的全部操做都會在線程終止以前,線程終止規則。
- 一個對象的終結操做必需在這個對象構造完成以後,也叫對象終結規則。
- 可傳遞性
8).Java中的volatile 變量是什麼
可見性,是指線程之間的可見性,一個線程修改的狀態對另外一個線程是可見的。也就是一個線程修改的結果。另外一個線程立刻就能看到。好比:用volatile修飾的變量,就會具備可見性。volatile修飾的變量不容許線程內部緩存和重排序,即直接修改內存。因此對其餘線程是可見的。可是這裏須要注意一個問題,volatile只能讓被他修飾內容具備可見性,但不能保證它具備原子性。好比 volatile int a = 0;以後有一個操做 a++;這個變量a具備可見性,可是a++ 依然是一個非原子操做,也就是這個操做一樣存在線程安全問題。
Java語言提供了一種稍弱的同步機制,即volatile變量,用來確保將變量的更新操做通知到其餘線程。當把變量聲明爲volatile類型後,編譯器與運行時都會注意到這個變量是共享的,所以不會將該變量上的操做與其餘內存操做一塊兒重排序。volatile變量不會被緩存在寄存器或者對其餘處理器不可見的地方,所以在讀取volatile類型的變量時總會返回最新寫入的值。在訪問volatile變量時不會執行加鎖操做,所以也就不會使執行線程阻塞,所以volatile變量是一種比sychronized關鍵字更輕量級的同步機制。
當對非 volatile 變量進行讀寫的時候,每一個線程先從內存拷貝變量到CPU緩存中。若是計算機有多個CPU,每一個線程可能在不一樣的CPU上被處理,這意味着每一個線程能夠拷貝到不一樣的 CPU cache 中。而聲明變量是 volatile 的,JVM 保證了每次讀變量都從內存中讀,跳過 CPU cache 這一步。
當一個變量定義爲 volatile 以後,將具有兩種特性:保證此變量對全部的線程的可見性;禁止指令重排序優化。有volatile修飾的變量,賦值後多執行了一個「load addl $0x0, (%esp)」操做,這個操做至關於一個內存屏障(指令重排序時不能把後面的指令重排序到內存屏障以前的位置),只有一個CPU訪問內存時,並不須要內存屏障;(什麼是指令重排序:是指CPU採用了容許將多條指令不按程序規定的順序分開發送給各相應電路單元處理)。
9).什麼是線程安全?Vector是一個線程安全類嗎?
若是你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。若是每次運行結果和單線程運行的結果是同樣的,並且其餘的變量的值也和預期的是同樣的,就是線程安全的。一個線程安全的計數器類的同一個實例對象在被多個線程使用的狀況下也不會出現計算失誤。很顯然你能夠將集合類分紅兩組,線程安全和非線程安全的。Vector 是用同步方法來實現線程安全的, 而和它類似的ArrayList不是線程安全的。
10).Java中什麼是競態條件? 舉個例子說明。
當某個計算正確性取決於多個線程的交替執行時序時, 就會發生靜態條件,即爭取的結果要取決於運氣, 最多見的靜態條件就是"先檢查後執行",經過一個可能失效的觀測結果來決定下一步的動做.
例如:
class
Counter {
protected
long
count =
0
;
public
void
add(
long
value) {
this
.count =
this
.count + value;
}
}
|
觀察線程A和B交錯執行會發生什麼,兩個線程分別加了2和3到count變量上,兩個線程執行結束後count變量的值應該等於5。然而因爲兩個線程是交叉執行的,兩個線程從內存中讀出的初始值都是0。而後各自加了2和3,並分別寫回內存。最終的值並非指望的5,而是最後寫回內存的那個線程的值,上面例子中最後寫回內存的是線程A,但實際中也多是線程B。若是沒有采用合適的同步機制,線程間的交叉執行狀況就沒法預料。
add()方法就是一個臨界區,它會產生競態條件。
11).一個線程運行時發生異常會怎樣
因此這裏存在兩種情形:
① 若是該異常被捕獲或拋出,則程序繼續運行。
② 若是異常沒有被捕獲該線程將會中止執行。
Thread.UncaughtExceptionHandler是用於處理未捕獲異常形成線程忽然中斷狀況的一個內嵌接口。當一個未捕獲異常將形成線程中斷的時候JVM會使用Thread.getUncaughtExceptionHandler()來查詢線程的UncaughtExceptionHandler,並將線程和異常做爲參數傳遞給handler的uncaughtException()方法進行處理。
12).線程間如何通訊,進程間如何通訊?
多線程間的通訊: 1). 共享變量; 2),wait, notify; 3)Lock/Condition機制; 4).管道機制,建立管道輸出流PipedOutputStream pos和管道輸入流PipedInputStream pis,將pos和pis匹配,pos.connect(pis),將pos賦給信息輸入線程,pis賦給信息獲取線程,就能夠實現線程間的通信了.
管道流雖然使用起來方便,可是也有一些缺點
1)管道流只能在兩個線程之間傳遞數據
線程consumer1和consumer2同時從pis中read數據,當線程producer往管道流中寫入一段數據後,每個時刻只有一個線程能獲取到數據,並非兩個線程都能獲取到producer發送來的數據,所以一個管道流只能用於兩個線程間的通信。不只僅是管道流,其餘IO方式都是一對一傳輸。
2)管道流只能實現單向發送,若是要兩個線程之間互通信,則須要兩個管道流.
進程間通訊:
1)管道(Pipe):管道可用於具備親緣關係進程間的通訊,容許一個進程和另外一個與它有共同祖先的進程之間進行通訊。
(2)命名管道(named pipe):命名管道克服了管道沒有名字的限制,所以,除具備管道所具備的功能外,它還容許無親緣關 系 進程間的通訊。命名管道在文件系統中有對應的文件名。命名管道經過命令mkfifo或系統調用mkfifo來建立。
(3)信號(Signal):信號是比較複雜的通訊方式,用於通知接受進程有某種事件發生,除了用於進程間通訊外,進程還能夠發送 信號給進程自己;linux除了支持Unix早期信號語義函數sigal外,還支持語義符合Posix.1標準的信號函數sigaction(實際上,該函數是基於BSD的,BSD爲了實現可靠信號機制,又可以統一對外接口,用sigaction函數從新實現了signal函數)。
(4)消息(Message)隊列:消息隊列是消息的連接表,包括Posix消息隊列system V消息隊列。有足夠權限的進程能夠向隊列中添加消息,被賦予讀權限的進程則能夠讀走隊列中的消息。消息隊列克服了信號承載信息量少,管道只能承載無格式字節流以及緩衝區大小受限等缺
(5)共享內存:使得多個進程能夠訪問同一塊內存空間,是最快的可用IPC形式。是針對其餘通訊機制運行效率較低而設計的。每每與其它通訊機制,如信號量結合使用,來達到進程間的同步及互斥。
(6)內存映射(mapped memory):內存映射容許任何多個進程間通訊,每個使用該機制的進程經過把一個共享的文件映射到本身的進程地址空間來實現它。
(7)信號量(semaphore):主要做爲進程間以及同一進程不一樣線程之間的同步手段。
(8)套接口(Socket):更爲通常的進程間通訊機制,可用於不一樣機器之間的進程間通訊。起初是由Unix系統的BSD分支開發出來的,但如今通常能夠移植到其它類Unix系統上:Linux和System V的變種都支持套接字。
13).Java中notify 和 notifyAll有什麼區別?
notify()¬ifyall()的共同點:均能喚醒正在等待的線程,而且均是最後只有一個線程獲取資源對象的鎖。
不一樣點:notify() 只能喚醒一個線程,而notifyall()可以喚醒全部的線程,當線程被喚醒之後全部被喚醒的線程競爭獲取資源對象的鎖,其中只有一個可以獲得對象鎖,執行代碼。
注意:wait()方法並非在等待資源的鎖,而是在等待被喚醒(notify()),一旦被喚醒後,被喚醒的線程就具有了資源鎖(由於無需競爭),直至再次執行wait()方法或者synchronized代碼塊執行完畢。
14).爲何wait, notify 和 notifyAll這些方法不在thread類裏面?
一個很明顯的緣由是JAVA提供的鎖是對象級的而不是線程級的,每一個對象都有鎖,經過線程得到。若是線程須要等待某些鎖那麼調用對象中的wait()方法就有意義了。若是wait()方法定義在Thread類中,線程正在等待的是哪一個鎖就不明顯了。簡單的說,因爲wait,notify和notifyAll都是鎖級別的操做,因此把他們定義在Object類中由於鎖屬於對象。
15).什麼是ThreadLocal變量?
ThreadLocal通常稱爲線程本地變量,它是一種特殊的線程綁定機制,將變量與線程綁定在一塊兒,爲每個線程維護一個獨立的變量副本。經過ThreadLocal能夠將對象的可見範圍限制在同一個線程內。
跳出誤區
須要重點強調的的是,不要拿ThreadLocal和synchronized作類比,由於這種比較壓根就是無心義的!sysnchronized是一種互斥同步機制,是爲了保證在多線程環境下對於共享資源的正確訪問。而ThreadLocal從本質上講,無非是提供了一個「線程級」的變量做用域,它是一種線程封閉(每一個線程獨享變量)技術,更直白點講,ThreadLocal能夠理解爲將對象的做用範圍限制在一個線程上下文中,使得變量的做用域爲「線程級」。
沒有ThreadLocal的時候,一個線程在其聲明週期內,可能穿過多個層級,多個方法,若是有個對象須要在此線程週期內屢次調用,且是跨層級的(線程內共享),一般的作法是經過參數進行傳遞;而ThreadLocal將變量綁定在線程上,在一個線程週期內,不管「你身處何地」,只需經過其提供的get方法就可輕鬆獲取到對象。極大地提升了對於「線程級變量」的訪問便利性。
16).Java中ThreadLocal變量, volatile變量, synchronized的區別
volatile主要是用來在多線程中同步變量。
在通常狀況下,爲了提高性能,每一個線程在運行時都會將主內存中的變量保存一份在本身的內存中做爲變量副本,可是這樣就很容易出現多個線程中保存的副本變量不一致,或與主內存的中的變量值不一致的狀況。
而當一個變量被volatile修飾後,該變量就不能被緩存到線程的內存中,它會告訴編譯器不要進行任何移出讀取和寫入操做的優化,換句話說就是不容許有不一樣於「主」內存區域的變量拷貝,因此當該變量有變化時,全部調用該變量的線程都會得到相同的值,這就確保了該變量在應用中的可視性(當一個任務作出了修改在應用中必須是可視的),同時性能也相應的下降了(仍是比synchronized高)。
但須要注意volatile只能確保操做的是同一塊內存,並不能保證操做的原子性。因此volatile通常用於聲明簡單類型變量,使得這些變量具備原子性,即一些簡單的賦值與返回操做將被確保不中斷。可是當該變量的值由自身的上一個決定時,volatile的做用就將失效,這是由volatile關鍵字的性質所決定的。
因此在volatile時必定要謹慎,千萬不要覺得用volatile修飾後該變量的全部操做都是原子操做,再也不須要synchronized關鍵字了。
ThreadLocal是一個線程的局部變量(其實就是一個Map),ThreadLocal會爲每一個使用該變量的線程提供獨立的變量副本,因此每個線程均可以獨立地改變本身的副本,而不會影響其它線程所對應的副本。這樣作其實就是以空間換時間的方式(與synchronized相反),以耗費內存爲代價,單大大減小了線程同步(如synchronized)所帶來性能消耗以及減小了線程併發控制的複雜度。
synchronized關鍵字是Java利用鎖的機制自動實現的,通常有同步方法和同步代碼塊兩種使用方式。Java中全部的對象都自動含有單一的鎖(也稱爲監視器),當在對象上調用其任意的synchronized方法時,此對象被加鎖(一個任務能夠屢次得到對象的鎖,計數會遞增),同時在線程從該方法返回以前,該對象內其餘全部要調用類中被標記爲synchronized的方法的線程都會被阻塞。
17).什麼是Future, FutureTask?
Future就是對於具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果。必要時能夠經過get方法獲取執行結果,該方法會阻塞直到任務返回結果。
Future類位於java.util.concurrent包下,它是一個接口:
1
2
3
4
5
6
7
8
|
public
interface
Future<V> {
boolean
cancel(
boolean
mayInterruptIfRunning);
boolean
isCancelled();
boolean
isDone();
V get()
throws
InterruptedException, ExecutionException;
V get(
long
timeout, TimeUnit unit)
throws
InterruptedException, ExecutionException, TimeoutException;
}
|
在Future接口中聲明瞭5個方法,下面依次解釋每一個方法的做用:
- cancel方法用來取消任務,若是取消任務成功則返回true,若是取消任務失敗則返回false。參數mayInterruptIfRunning表示是否容許取消正在執行卻沒有執行完畢的任務,若是設置true,則表示能夠取消正在執行過程當中的任務。若是任務已經完成,則不管mayInterruptIfRunning爲true仍是false,此方法確定返回false,即若是取消已經完成的任務會返回false;若是任務正在執行,若mayInterruptIfRunning設置爲true,則返回true,若mayInterruptIfRunning設置爲false,則返回false;若是任務尚未執行,則不管mayInterruptIfRunning爲true仍是false,確定返回true。
- isCancelled方法表示任務是否被取消成功,若是在任務正常完成前被取消成功,則返回 true。
- isDone方法表示任務是否已經完成,若任務完成,則返回true;
- get()方法用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回;
- get(long timeout, TimeUnit unit)用來獲取執行結果,若是在指定時間內,還沒獲取到結果,就直接返回null。
也就是說Future提供了三種功能:
1)判斷任務是否完成;
2)可以中斷任務;
3)可以獲取任務執行結果。
public
class
FutureTask<V>
implements
RunnableFuture<V>
|
FutureTask類實現了RunnableFuture接口,咱們看一下RunnableFuture接口的實現:
1
2
3
|
public
interface
RunnableFuture<V>
extends
Runnable, Future<V> {
void
run();
}
|
在Java併發程序中FutureTask表示一個能夠取消的異步運算。它有啓動和取消運算、查詢運算是否完成和取回運算結果等方法。只有當運算完成的時候結果才能取回,若是運算還沒有完成get方法將會阻塞。一個FutureTask對象能夠對調用了Callable和Runnable的對象進行包裝,因爲FutureTask也是調用了Runnable接口因此它能夠提交給Executor來執行。
18).Java中interrupted 和 isInterruptedd方法的區別?
interrupted() 和 isInterrupted()的主要區別是前者會將中斷狀態清除然後者不會。Java多線程的中斷機制是用內部標識來實現的,調用Thread.interrupt()來中斷一個線程就會設置中斷標識爲true。當中斷線程調用靜態方法Thread.interrupted()來檢查中斷狀態時,中斷狀態會被清零。而非靜態方法isInterrupted()用來查詢其它線程的中斷狀態且不會改變中斷狀態標識。簡單的說就是任何拋出InterruptedException異常的方法都會將中斷狀態清零。不管如何,一個線程的中斷狀態有有可能被其它線程調用中斷來改變。
interrupt方法是用於中斷線程的,調用該方法的線程的狀態將被置爲"中斷"狀態。注意:線程中斷僅僅是設置線程的中斷狀態位,不會中止線程。須要用戶本身去監視線程的狀態爲並作處理。支持線程中斷的方法(也就是線程中斷後會拋出InterruptedException的方法,好比這裏的sleep,以及Object.wait等方法)就是在監視線程的中斷狀態,一旦線程的中斷狀態被置爲「中斷狀態」,就會拋出中斷異常。
interrupted方法的實現:
- public static boolean interrupted() {
- return currentThread().isInterrupted(true);
- }
和isInterrupted的實現
- public boolean isInterrupted() {
- return isInterrupted(false);
- }
這兩個方法一個是static的,一個不是,但實際上都是在調用同一個方法,只是interrupted方法傳入的參數爲true,而inInterrupted傳入的參數爲false。這是一個native方法,看不到源碼沒有關係,參數名字ClearInterrupted已經清楚的表達了該參數的做用----是否清除中斷狀態。方法的註釋也清晰的表達了「中斷狀態將會根據傳入的ClearInterrupted參數值肯定是否重置」。因此,靜態方法interrupted將會清除中斷狀態(傳入的參數ClearInterrupted爲true),而實例方法isInterrupted則不會(傳入的參數ClearInterrupted爲false)。
19).Java中volatile和原子類?
若是一個變量加了volatile關鍵字,就會告訴編譯器和JVM的內存模型:這個變量是對全部線程共享的、可見的,每次jvm都會讀取最新寫入的值並使其最新值在全部CPU可見。volatile彷佛是有時候能夠代替簡單的鎖,彷佛加了volatile關鍵字就省掉了鎖。但又說volatile不能保證原子性(java程序員很熟悉這句話:volatile僅僅用來保證該變量對全部線程的可見性,但不保證原子性)。若是你的字段是volatile,Java內存模型將在寫操做後插入一個寫屏障指令,在讀操做前插入一個讀屏障指令。這意味着若是你對一個volatile字段進行寫操做,你必須知道:一、一旦你完成寫入,任何訪問這個字段的線程將會獲得最新的值。二、在你寫入前,會保證全部以前發生的事已經發生,而且任何更新過的數據值也是可見的,由於內存屏障會把以前的寫入值都刷新到緩存。
volatile爲何沒有原子性?
明白了內存屏障(memory barrier)這個CPU指令,回到前面的JVM指令:從Load到store到內存屏障,一共4步,其中最後一步jvm讓這個最新的變量的值在全部線程可見,也就是最後一步讓全部的CPU內核都得到了最新的值,但中間的幾步(從Load到Store)是不安全的,中間若是其餘的CPU修改了值將會丟失。
原子類保證瞭解決了上述的volatile的原子性沒有保證的問題, 用到了CAS操做,
由於CAS是基於樂觀鎖的,也就是說當寫入的時候,若是寄存器舊值已經不等於現值,說明有其餘CPU在修改,那就繼續嘗試。因此這就保證了操做的原子性。

19).爲何wait和notify方法要在同步塊中調用?
主要是由於Java API強制要求這樣作,若是你不這麼作,你的代碼會拋出IllegalMonitorStateException異常。還有一個緣由是爲了不wait和notify之間產生競態條件。
20).Java中的同步集合與併發集合有什麼區別
Synchronized vs Concurrent Collections
無論是同步集合仍是併發集合他們都支持線程安全,他們之間主要的區別體如今性能和可擴展性,還有他們如何實現的線程安全。同步HashMap, Hashtable, HashSet, Vector, ArrayList 相比他們併發的實現(好比:ConcurrentHashMap, CopyOnWriteArrayList, CopyOnWriteHashSet)會慢得多。形成如此慢的主要緣由是鎖, 同步集合會把整個Map或List鎖起來,而併發集合不會。併發集合實現線程安全是經過使用先進的和成熟的技術像鎖剝離。好比ConcurrentHashMap 會把整個Map 劃分紅幾個片斷,只對相關的幾個片斷上鎖,同時容許多線程訪問其餘未上鎖的片斷。
一樣的,CopyOnWriteArrayList 容許多個線程以非同步的方式讀,當有線程寫的時候它會將整個List複製一個副本給它。
若是在讀多寫少這種對併發集合有利的條件下使用併發集合,這會比使用同步集合更具備可伸縮性。
同步集合與併發集合都爲多線程和併發提供了合適的線程安全的集合,不過併發集合的可擴展性更高。在Java1.5以前程序員們只有同步集合來用且在多線程併發的時候會致使爭用,阻礙了系統的擴展性。Java5介紹了併發集合像ConcurrentHashMap,不只提供線程安全還用鎖分離和內部分區等現代技術提升了可擴展性。
21).Java中堆和棧有什麼不一樣?
由於棧是一塊和線程緊密相關的內存區域。每一個線程都有本身的棧內存,用於存儲本地變量,方法參數和棧調用,一個線程中存儲的變量對其它線程是不可見的。而堆是全部線程共享的一片公用內存區域。對象都在堆裏建立,爲了提高效率線程會從堆中弄一個緩存到本身的棧,若是多個線程使用該變量就可能引起問題,這時volatile 變量就能夠發揮做用了,它要求線程從主存中讀取變量的值。
22).什麼是線程池? 爲何要使用它?
建立線程要花費昂貴的資源和時間,若是任務來了才建立線程那麼響應時間會變長,並且一個進程能建立的線程數有限。爲了不這些問題,在程序啓動的時候就建立若干線程來響應處理,它們被稱爲線程池,裏面的線程叫工做線程。從JDK1.5開始,Java API提供了Executor框架讓你能夠建立不一樣的線程池。好比單線程池,每次處理一個任務;數目固定的線程池或者是緩存線程池(一個適合不少生存期短的任務的程序的可擴展線程池)。
22).實現生產者消費者模式
package ProducterAndConsumer.Version1;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 生產者
* 生產者消費者模型
*/
public class Producer implements Runnable {
private volatile boolean isRunning = true;
private BlockingQueue<PCData> queue;// 內存緩衝區
private static AtomicInteger count = new AtomicInteger();// 總數 原子操做
private static final int SLEEPTIME = 1000;
public Producer(BlockingQueue<PCData> queue) {
this.queue = queue;
}
@Override
public void run() {
PCData data = null;
Random r = new Random();
System.out.println("start producting id:" + Thread.currentThread().getId());
try {
while (isRunning) {
Thread.sleep(r.nextInt(SLEEPTIME));
data = new PCData(count.incrementAndGet());
System.out.println(data + " 加入隊列");
if (!queue.offer(data, 2, TimeUnit.SECONDS)) {
System.err.println(" 加入隊列失敗");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
public void stop() {
isRunning = false;
}
}
package ProducterAndConsumer.Version1;
/**
* 消費者
*
*/
import java.text.MessageFormat;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
public class Consumer implements Runnable{
private BlockingQueue<PCData> queue;
private static final int SLEEPTIME = 1000;
public Consumer(BlockingQueue<PCData> queue){
this.queue = queue;
}
@Override
public void run() {
System.out.println("start Consumer id :"+Thread.currentThread().getId());
Random r = new Random();
try{
while(true){
PCData data = queue.take();
if(data != null)
{
int re = data.getData() * data.getData();
System.out.println(MessageFormat.format("{0}*{1}={2}", data.getData(),data.getData(),re));
Thread.sleep(r.nextInt(SLEEPTIME));
}
}
}catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
package ProducterAndConsumer.Version1;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingDeque;
/**
* 主函數
* @author ctk
*
*/
public class Main {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<PCData> queue = new LinkedBlockingDeque<>(10);
Producer p1 = new Producer(queue);
Producer p2 = new Producer(queue);
Producer p3 = new Producer(queue);
Consumer c1 = new Consumer(queue);
Consumer c2 = new Consumer(queue);
Consumer c3 = new Consumer(queue);
ExecutorService service = Executors.newCachedThreadPool();
service.execute(p1);
service.execute(p2);
service.execute(p3);
service.execute(c1);
service.execute(c2);
service.execute(c3);
Thread.sleep(10*1000);
p1.stop();
p2.stop();
p3.stop();
Thread.sleep(3000);
service.shutdown();
}
}
package ProducterAndConsumer.Version1;
/**
* 容器數據類型
* @author ctk
*
*/
public class PCData {
private final int intData;
public PCData(int d){
intData = d;
}
public PCData(String d){
intData = Integer.valueOf(d);
}
public int getData(){
return intData;
}
@Override
public String toString(){
return "data:"+intData;
}
}
23).如何避免死鎖?
Java多線程中的死鎖
死鎖是指兩個或兩個以上的進程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。這是一個嚴重的問題,由於死鎖會讓你的程序掛起沒法完成任務,死鎖的發生必須知足如下四個條件:
- 互斥條件:一個資源每次只能被一個進程使用。
- 請求與保持條件:一個進程因請求資源而阻塞時,對已得到的資源保持不放。
- 不剝奪條件:進程已得到的資源,在末使用完以前,不能強行剝奪。
- 循環等待條件:若干進程之間造成一種頭尾相接的循環等待資源關係。
避免死鎖最簡單的方法就是阻止循環等待條件,將系統中全部的資源設置標誌位、排序,規定全部的進程申請資源必須以必定的順序(升序或降序)作操做來避免死鎖。
24).Java中活鎖和死鎖有什麼區別?
活鎖和死鎖相似,不一樣之處在於處於活鎖的線程或進程的狀態是不斷改變的,活鎖能夠認爲是一種特殊的飢餓。一個現實的活鎖例子是兩我的在狹小的走廊碰到,兩我的都試着避讓對方好讓彼此經過,可是由於避讓的方向都同樣致使最後誰都不能經過走廊。簡單的說就是,活鎖和死鎖的主要區別是前者進程的狀態能夠改變可是卻不能繼續執行。
25)怎麼檢測一個線程是否擁有鎖?
在java.lang.Thread中有一個方法叫holdsLock(),它返回true若是當且僅當當前線程擁有某個具體對象的鎖。
26).JVM中哪一個參數是用來控制線程的棧堆棧小的
-Xss參數用來控制線程的堆棧大小。
27).Java中synchronized 和 ReentrantLock 有什麼不一樣?
類似點:
這兩種同步方式有不少類似之處,它們都是加鎖方式同步,並且都是阻塞式的同步,也就是說當若是一個線程得到了對象鎖,進入了同步塊,其餘訪問該同步塊的線程都必須阻塞在同步塊外面等待,而進行線程阻塞和喚醒的代價是比較高的(操做系統須要在用戶態與內核態之間來回切換,代價很高,不過能夠經過對鎖優化進行改善)。
區別:
這兩種方式最大區別就是對於Synchronized來講,它是java語言的關鍵字,是原生語法層面的互斥,須要jvm實現。而ReentrantLock它是JDK 1.5以後提供的API層面的互斥鎖,須要lock()和unlock()方法配合try/finally語句塊來完成。
Synchronized進過編譯,會在同步塊的先後分別造成monitorenter和monitorexit這個兩個字節碼指令。在執行monitorenter指令時,首先要嘗試獲取對象鎖。若是這個對象沒被鎖定,或者當前線程已經擁有了那個對象鎖,把鎖的計算器加1,相應的,在執行monitorexit指令時會將鎖計算器就減1,當計算器爲0時,鎖就被釋放了。若是獲取對象鎖失敗,那當前線程就要阻塞,直到對象鎖被另外一個線程釋放爲止。
因爲ReentrantLock是java.util.concurrent包下提供的一套互斥鎖,相比Synchronized,ReentrantLock類提供了一些高級功能,主要有如下3項:
1.等待可中斷,持有鎖的線程長期不釋放的時候,正在等待的線程能夠選擇放棄等待,這至關於Synchronized來講能夠避免出現死鎖的狀況。
2.公平鎖,多個線程等待同一個鎖時,必須按照申請鎖的時間順序得到鎖,Synchronized鎖非公平鎖,ReentrantLock默認的構造函數是建立的非公平鎖,能夠經過參數true設爲公平鎖,但公平鎖表現的性能不是很好。
3.鎖綁定多個條件,一個ReentrantLock對象能夠同時綁定對個對象。
28).有三個線程T1,T2,T3,怎麼確保它們按順序執行?
在多線程中有多種方法讓線程按特定順序執行,你能夠用線程類的join()方法在一個線程中啓動另外一個線程,另一個線程完成該線程繼續執行。爲了確保三個線程的順序你應該先啓動最後一個(T3調用T2,T2調用T1),這樣T1就會先完成而T3最後完成。
29).Thread類中的yield方法有什麼做用?
yield()方法可讓當前正在執行的線程暫停,但它不會阻塞該線程,它只是將該線程從運行狀態轉入就緒狀態。
只是讓當前的線程暫停一下,讓系統的線程調度器從新調度一次。
頗有可能,當某個線程調用了yield()方法暫停以後進入就緒狀態,它又立刻搶佔了CPU的執行權,繼續執行。
實際上,當某個線程調用了yield()方法暫停以後,只有優先級與當前線程相同,或者優先級比當前線程更高的處於就緒狀態的線程纔會得到執行的機會。
【關於sleep和yield的區別】
1.sleep()方法暫停當前線程後,會給其餘線程執行機會,線程優先級對此沒有影響。
yield()方法會給優先級相同或更高的線程更高的執行機會。
2.sleep()方法會將線程轉入阻塞狀態,直到阻塞時間結束,纔會轉入就緒狀態。
yield()方法會將當前線程直接轉入就緒狀態。
3.sleep()方法聲明拋出了InterruptedException異常,因此調用sleep()方法時要麼捕捉該異常,要麼顯示聲明拋出該異常。
yield()方法則沒有聲明拋出任何異常。
4.sleep()方法比yield()方法有更好的移植性,一般不建議使用yield()方法來控制併發線程的執行.
30).Java中Semaphore是什麼?
Java中的Semaphore是一種新的同步類,它是一個計數信號。從概念上講,從概念上講,信號量維護了一個許可集合。若有必要,在許可可用前會阻塞每個 acquire(),而後再獲取該許可。每一個 release()添加一個許可,從而可能釋放一個正在阻塞的獲取者。可是,不使用實際的許可對象,Semaphore只對可用許可的號碼進行計數,並採起相應的行動。信號量經常用於多線程的代碼中,好比數據庫鏈接池。
31).若是你提交任務時,線程池隊列已滿。會時發會生什麼?
若是一個任務不能被調度執行那麼ThreadPoolExecutor’s submit()方法將會拋出一個RejectedExecutionException異常。
32).Java線程池中submit() 和 execute()方法有什麼區別?
1.對返回值的處理不一樣
execute方法不關心返回值。
submit方法有返回值,Future.
2.對異常的處理不一樣
excute方法會拋出異常。
sumbit方法不會拋出異常。除非你調用Future.get()
33).什麼是阻塞式方法?
阻塞式方法是指程序會一直等待該方法完成期間不作其餘事情,ServerSocket的accept()方法就是一直等待客戶端鏈接。這裏的阻塞是指調用結果返回以前,當前線程會被掛起,直到獲得結果以後纔會返回。此外,還有異步和非阻塞式方法在任務完成前就返回。
34)Swing是線程安全的嗎? 爲何?
Swing不是線程安全的,可是你應該解釋這麼回答的緣由即使面試官沒有問你爲何。當咱們說swing不是線程安全的經常提到它的組件,這些組件不能在多線程中進行修改,全部對GUI組件的更新都要在AWT線程中完成,而Swing提供了同步和異步兩種回調方法來進行更新。
35).Java中invokeAndWait 和 invokeLater有什麼區別?
invokeAndWait:後面的程序必須等這個線程(參數中的線程)的東西執行完才能執行
invokeLater:後面的程序和這個參數的線程對象能夠並行,異步地執行
invokeLater通常用於在線程裏修改swing組件的外觀,由於swing組件是非同步的,因此不能在線程中直接修改,會不一樣步,得不到指望的效果,因此要把修改外觀的代碼放在一個單獨的線程中,交給invokeLater:後面的程序和這個參數的線程對象能夠並行,異步地執行.
36).Swing API中那些方法是線程安全的?
這個問題又提到了swing和線程安全,雖然組件不是線程安全的可是有一些方法是能夠被多線程安全調用的,好比repaint(), revalidate()。 JTextComponent的setText()方法和JTextArea的insert() 和 append() 方法也是線程安全的。
37).如何在Java中建立Immutable(不可變)對象?
Immutable對象能夠在沒有同步的狀況下共享,下降了對該對象進行併發訪問時的同步化開銷。但是Java沒有@Immutable這個註解符,要建立不可變類,要實現下面幾個步驟:經過構造方法初始化全部成員、對變量不要提供setter方法、將全部的成員聲明爲私有的,這樣就不容許直接訪問這些成員、在getter方法中,不要直接返回對象自己,而是克隆對象,並返回對象的拷貝。
38).Java中的ReadWriteLock是什麼?
Java中的ReadWriteLock是Java 5 中新增的一個接口,一個ReadWriteLock維護一對關聯的鎖,一個用於只讀操做一個用於寫。在沒有寫線程的狀況下一個讀鎖可能會同時被多個讀線程持有。寫鎖是獨佔的,你可使用JDK中的ReentrantReadWriteLock來實現這個規則,它最多支持65535個寫鎖和65535個讀鎖。
39).多線程中的忙循環是什麼?
忙循環就是程序員用循環讓一個線程等待,不像傳統方法wait(), sleep() 或 yield() 它們都放棄了CPU控制,而忙循環不會放棄CPU,它就是在運行一個空循環。這麼作的目的是爲了保留CPU緩存,在多核系統中,一個等待線程醒來的時候可能會在另外一個內核運行,這樣會重建緩存。爲了不重建緩存和減小等待重建的時間就可使用它了。
40).若是同步塊內的線程拋出異常會發生什麼?
不管你的同步塊是正常仍是異常退出的,裏面的線程都會釋放鎖.
41).單例模式的雙檢鎖是什麼?
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
if (instance == null) //2
instance = new Singleton(); //3
}
}
return instance;
}
雙重檢查鎖定背後的理論是:在 //2 處的第二次檢查使(如清單 3 中那樣)建立兩個不一樣的 Singleton
對象成爲不可能。假設有下列事件序列:
- 線程 1 進入
getInstance()
方法。
- 因爲
instance
爲 null
,線程 1 在 //1 處進入 synchronized
塊。
- 線程 1 被線程 2 預佔。
- 線程 2 進入
getInstance()
方法。
- 因爲
instance
仍舊爲 null
,線程 2 試圖獲取 //1 處的鎖。然而,因爲線程 1 持有該鎖,線程 2 在 //1 處阻塞。
- 線程 2 被線程 1 預佔。
- 線程 1 執行,因爲在 //2 處實例仍舊爲
null
,線程 1 還建立一個 Singleton
對象並將其引用賦值給 instance
。
- 線程 1 退出
synchronized
塊並從 getInstance()
方法返回實例。
- 線程 1 被線程 2 預佔。
- 線程 2 獲取 //1 處的鎖並檢查
instance
是否爲 null
。
- 因爲
instance
是非 null
的,並無建立第二個 Singleton
對象,由線程 1 建立的對象被返回。
雙重檢查鎖定背後的理論是完美的。不幸地是,現實徹底不一樣。雙重檢查鎖定的問題是:並不能保證它會在單處理器或多處理器計算機上順利運行。
雙重檢查鎖定失敗的問題並不歸咎於 JVM 中的實現 bug,而是歸咎於 Java 平臺內存模型。內存模型容許所謂的「無序寫入」,這也是這些習語失敗的一個主要緣由。
無序寫入
爲解釋該問題,須要從新考察上述清單 4 中的 //3 行。此行代碼建立了一個 Singleton
對象並初始化變量 instance
來引用此對象。這行代碼的問題是:在 Singleton
構造函數體執行以前,變量 instance
可能成爲非 null
的。
42).寫出3條你遵循的多線程最佳實踐
這種問題我最喜歡了,我相信你在寫併發代碼來提高性能的時候也會遵循某些最佳實踐。如下三條最佳實踐我以爲大多數Java程序員都應該遵循:
- 給你的線程起個有意義的名字。
這樣能夠方便找bug或追蹤。OrderProcessor, QuoteProcessor or TradeProcessor 這種名字比 Thread-1. Thread-2 and Thread-3 好多了,給線程起一個和它要完成的任務相關的名字,全部的主要框架甚至JDK都遵循這個最佳實踐。
- 避免鎖定和縮小同步的範圍
鎖花費的代價高昂且上下文切換更耗費時間空間,試試最低限度的使用同步和鎖,縮小臨界區。所以相對於同步方法我更喜歡同步塊,它給我擁有對鎖的絕對控制權。
- 多用同步類少用wait 和 notify
首先,CountDownLatch, Semaphore, CyclicBarrier 和 Exchanger 這些同步類簡化了編碼操做,而用wait和notify很難實現對複雜控制流的控制。其次,這些類是由最好的企業編寫和維護在後續的JDK中它們還會不斷優化和完善,使用這些更高等級的同步工具你的程序能夠不費吹灰之力得到優化。
- 多用併發集合少用同步集合
這是另一個容易遵循且受益巨大的最佳實踐,併發集合比同步集合的可擴展性更好,因此在併發編程時使用併發集合效果更好。若是下一次你須要用到map,你應該首先想到用ConcurrentHashMap。
43).Java中的fork join框架是什麼?
fork join框架是JDK7中出現的一款高效的工具,Java開發人員能夠經過它充分利用現代服務器上的多處理器。它是專門爲了那些能夠遞歸劃分紅許多子模塊設計的,目的是將全部可用的處理能力用來提高程序的性能。fork join框架一個巨大的優點是它使用了工做竊取算法,能夠完成更多任務的工做線程能夠從其它線程中竊取任務來執行。
44).Java多線程中調用wait() 和 sleep()方法有什麼不一樣?
Java程序中wait 和 sleep都會形成某種形式的暫停,它們能夠知足不一樣的須要。wait()方法用於線程間通訊,若是等待條件爲真且其它線程被喚醒時它會釋放鎖,而sleep()方法僅僅釋放CPU資源或者讓當前線程中止執行一段時間,但不會釋放鎖。