一、Thread 基礎知識
1、執行緒的狀態
- 建立(new)狀態:準備好了一個多執行緒物件。
- 就緒(runable)狀態:呼叫了start()方法,等待CPU的排程。
- 執行(running)狀態: 執行run()方法.
- 阻塞(blocked)狀態:暫時停止,可能將資源交給其他執行緒。
- 終止(dead)狀態:執行緒銷燬。
2、執行緒執行流程
- 1、執行緒建立後不會立即進入就緒狀態,因為執行緒執行需要一些條件(比如劃分一塊記憶體空間)。只有執行緒就緒需要的條件都準備好了,才能進入就緒狀態。
- 2、當執行緒進入就緒狀態後,不代表立刻就能獲取CPU執行時間,也許此時CPU正在執行其他的事情,因此它要等待。當得到CPU執行時間之後,執行緒便真正進入執行狀態。
- 3、執行緒在執行狀態過程中,可能有多個原因導致當前執行緒不繼續執行下去。進入阻塞狀態。time waiting(睡眠或等待一定的事件)、waiting(等待被喚醒)、blocked(阻塞)
- 4、當由於突然中斷或者子任務執行完畢,執行緒就會被消亡。
3、sleep和wait的區別:
- wait是Thread的方法,sleep是Object中的方法。
- Thread.sleep() 不會改變鎖的行為,不會讓執行緒釋放鎖。
- Thread.sleep和Object.wait都會暫停當前的執行緒,呼叫wait後, 需要別的執行緒執行notify/notifyAll才能夠重新獲得CPU執行時間.
4、停止一個執行緒
- 1、使用退出標誌,使執行緒正常退出,也就是當run方法完成後執行緒終止
- 2、使用stop方法強行終止執行緒,但是不推薦使用這個方法,是作廢過期的方法,使用他們可能產生不可預料的結果。
- 3、使用interrupt方法中斷執行緒,但這個不會終止一個正在執行的執行緒,還需要加入一個判斷才可以完成執行緒的停止。
二、Android 面試題
1、開啟執行緒的三種方式?
- 1)繼承thread 類
public class MyThread extends Thread {
private int i;
@Override
public void run() {
...
}
}
複製程式碼
-
- 實現Runable介面
//1實現Runnable介面
public class SecondThread implements Runnable{
public void run() {
}
}
//2使用
SecondThread s1=new SecondThread();
Thread t1=new Thread(s1,"執行緒1");
t1.start();
複製程式碼
2、執行緒和程式的區別?
- 1)一個程式至少有一個程式.並且執行緒共享整個程式的資源(暫存器、堆疊、上下文),一個進行至少包括一個執行緒。
- 2)程式在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體,從而極大地提高了程式的執行效率。
- 3)程式是資源的分配和排程的一個獨立單元,而執行緒是CPU排程的基本單元
- 4)執行緒是輕量級的程式,它的建立和銷燬所需要的時間比程式小很多,所以系統中的執行功能通常是建立執行緒去完成的
3、為什麼要有執行緒,而不是僅僅用程式?
- 1)程式只能在一個時間幹一件事,如果想同時幹兩件事或多件事,程式就無能為力了。
- 2)程式在執行的過程中如果阻塞,例如等待輸入,整個程式就會掛起,即使程式中有些工作不依賴於輸入的資料,也將無法執行。
4、run()和start()方法區別
- 1)用start方法來啟動執行緒,真正實現了多執行緒執行。
- 2)run方法只是thread的一個普通方法呼叫,還是在主執行緒裡執行。
5、如何控制某個方法允許併發訪問執行緒的個數?
- 通過samePhore方法
- semaphore.acquire() 請求一個訊號量,這時候的訊號量個數-1(一旦沒有可使用的訊號量,也即訊號量個數變為負數時,再次請求的時候就會阻塞,直到其他執行緒釋放了訊號量)
- semaphore.release() 釋放一個訊號量,此時訊號量個數+1
public class SemaphoreTest {
private Semaphore mSemaphore = new Semaphore(5);
public void run(){
for(int i=0; i< 100; i++){
new Thread(new Runnable() {
@Override
public void run() {
test();
}
}).start();
}
}
private void test(){
try {
mSemaphore.acquire(); //Thread.interrupted()阻塞執行緒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 進來了");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 出去了");
mSemaphore.release();
}
}
複製程式碼
6、在Java中wait和seelp方法的不同;
-
- Thread類的方法:sleep(),yield()等 ,Object的方法:wait()和notify()等
-
- Thread.sleep不會導致鎖行為的改變,如果當前執行緒是擁有鎖的,那麼Thread.sleep不會讓執行緒釋放鎖。呼叫wait()方法的時候,執行緒會放棄物件鎖,
-
- 而且wait存在notify方法來喚醒呼叫wait的執行緒,這個是sleep沒有的。
7、談談wait/notify關鍵字的理解
-
- wait( ),notify( ),notifyAll( )都不屬於Thread類,而是屬於Object基礎類,每個物件都有wait( ),notify( ),notifyAll( ) 的功能,因為每個物件都有鎖,鎖是每個物件的基礎,當然操作鎖的方法也是最基礎了。
-
- notify( ) 方法只會通知等待佇列中的第一個相關執行緒(不會通知優先順序比較高的執行緒)
-
- notifyAll( ) 通知所有等待該競爭資源的執行緒(也不會按照執行緒的優先順序來執行)
-
- wait( ) 導致當前的執行緒等待,直到其他執行緒呼叫此物件的notify( ) 方法或 notifyAll( ) 方法
8、什麼導致執行緒阻塞?
- 1)特點:該執行緒放棄CPU的使用,暫停執行,只有等到導致阻塞的原因消除之後才恢復執行。或者是被其他的執行緒中斷,該執行緒也會退出阻塞狀態,同時丟擲InterruptedException
- 2)執行緒執行了Thread.sleep(intmillsecond);方法,當前執行緒放棄CPU,睡眠一段時間,然後再恢復執行
- 3)執行緒執行了一個物件的wait()方法,直接進入阻塞狀態,等待其他執行緒執行notify()或者notifyAll()方法
- 4)執行緒執行某些IO操作,因為等待相關的資源而進入了阻塞狀態。比如說監聽system.in,但是尚且沒有收到鍵盤的輸入,則進入阻塞狀態。
- 5)執行緒執行一段同步程式碼,但是尚且無法獲得相關的同步鎖,只能進入阻塞狀態,等到獲取了同步鎖,才能回覆執行。
9、執行緒如何關閉?
- 1)關於執行緒的關閉,可能會用stop() 方法,但是stop是執行緒不安全的,一般採用interrupt,判斷執行緒是否中止採用isInterrupted,
Thread thread = null;
thread = new Thread(new Runnable() {
@Override
public void run() {
/*
* 在這裡為一個迴圈,條件是判斷執行緒的中斷標誌位是否中斷
*/
while (true&&(!Thread.currentThread().isInterrupted())) {
try {
Log.i("tag","執行緒執行中"+Thread.currentThread().getId());
// 每執行一次暫停40毫秒
//當sleep方法丟擲InterruptedException 中斷狀態也會被清掉
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
//如果丟擲異常則再次設定中斷請求
Thread.currentThread().interrupt();
}
}
}
});
thread.start();
//觸發條件設定中斷
thread.interrupt();
複製程式碼
10、講一下java中的同步的方法
-
- 同步方法
即有synchronized關鍵字修飾的方法。
由於java的每個物件都有一個內建鎖,當用此關鍵字修飾方法時,
內建鎖會保護整個方法。在呼叫該方法前,需要獲得內建鎖,否則就處於阻塞狀態。
複製程式碼
-
- 同步程式碼塊
即有synchronized關鍵字修飾的語句塊。
被該關鍵字修飾的語句塊會自動被加上內建鎖,從而實現同步
程式碼如:
synchronized(object){
}
注:同步是一種高開銷的操作,因此應該儘量減少同步的內容。
通常沒有必要同步整個方法,使用synchronized程式碼塊同步關鍵程式碼即可。
複製程式碼
- 3)使用特殊域變數(volatile)實現執行緒同步
複製程式碼
- 4)使用重入鎖實現執行緒同步
在JavaSE5.0中新增了一個java.util.concurrent包來支援同步。ReentrantLock類是可重入、互斥、實現了Lock介面的鎖, 它與使用synchronized方法和快具有相同的基本行為和語義,並且擴充套件了其能力。
ReenreantLock類的常用方法有:
ReentrantLock() : 建立一個ReentrantLock例項
lock() : 獲得鎖
unlock() : 釋放鎖
注:ReentrantLock()還有一個可以建立公平鎖的構造方法,但由於能大幅度降低程式執行效率,不推薦使用
複製程式碼
- 5)使用區域性變數ThreadLocal實現.
- 總結:volatile和ThreadLoacl都是通過修飾執行緒共享的變數來實現同步.剩餘3種類似個域加鎖.
11、資料一致性如何保證?
- 這個沒使用過!不會
12、如何保證執行緒安全?
- 確保執行緒安全的方法:競爭與原子操作、同步與鎖、可重入、過度優化
- 1)競爭與原子操作:
多執行緒訪問和修改一個資料,可能造成嚴重的後果。原因是很多的操作被作業系統編譯為彙編程式碼後不止一條指令,因此執行的時候可能執行到一半就被排程系統打亂執行別的程式碼了。一般單指令的操作稱為原子操作,不管怎麼樣,單指令的執行是不會被打亂的。
因此為了避免出現多執行緒運算元據的出現異常,Linux系統提供了一些常用操作的原子指令,確保了執行緒的安全。 - 2)同步與鎖:
在一個執行緒訪問資料未結束的時候,其他執行緒不得對同一個資料進行訪問,同步的最常用的方法是使用鎖(Lock),- 1、每個執行緒訪問資料或者資源前首先試圖獲取鎖,
- 2、訪問結束後釋放鎖 ,
- 3、在鎖已經被佔用的時候試圖獲取鎖時,執行緒會等待,直到鎖重新可以用。
- 3)可重入:
一個函式被重入,表示這個函式沒有執行完成,但由於外部因素或內部因素,又一次進入該函式執行。什麼是可重入函式?
可重入是併發安全的強力保障,一個可重入的函式可以在多執行緒環境下放心使用。
//只使用非靜態區域性變數;不呼叫不可重入的函式。
public class Reentrant1 {
private int count = 0;
/**
* 不可重入
* 當有多個執行緒呼叫同一個Reentrant物件的increament(),輸出數值是不可預測的。count可能是1,也可能是任何正整數。
*/
public void increament(){
count ++;
System.out.println(count);
}
/**
* 可重入
* 無論多少執行緒呼叫同一個Reentrant對的象decreament方法,輸出數值都是傳入引數length的值減1。
*/
public void decreament(int length){
length--;
System.out.println(length);
}
}
複製程式碼
- 4)過度優化
在很多情況下,即使我們合理地使用了鎖,也不一定能夠保證執行緒安全,因此,我們可能對程式碼進行過度的優化以確保執行緒安全。 可以使用volatile關鍵字試圖阻止過度優化,它可以做兩件事:- 第一,阻止編譯器為了提高速度將一個變數快取到暫存器而不寫回;
- 第二,阻止編譯器調整操作volatile變數的指令順序。
13、如何實現執行緒同步?
同 問題10
複製程式碼
14、兩個程式同時要求寫或者讀,能不能實現?如何防止程式的同步?
15、執行緒間操作List
通過 :Collections.synchronizedList(new ArrayList());
public class MultiThread {
public static final List<Long> list = Collections.synchronizedList(new ArrayList<Long>());
public static void main(String[] args) {
for(int i = 1;i<=100;i++){
list.add(Long.valueOf(i));
}
MyThread myThread = new MultiThread().new MyThread();
Thread t1 = new Thread(myThread);
t1.setName("執行緒1");
t1.start();
Thread t2 = new Thread(myThread);
t2.setName("執行緒2");
t2.start();
Thread t3 = new Thread(myThread);
t3.setName("執行緒3");
t3.start();
Thread t4 = new Thread(myThread);
t4.setName("執行緒4");
t4.start();
}
public class MyThread implements Runnable{
@Override
public void run() {
for(int i = 0;i<list.size();i++){
// 同步list,列印資料並刪除該資料
synchronized (list) {
try {
//當前執行緒睡眠,讓其它執行緒獲得執行機會
Thread.sleep(100);
} catch (Exception e) {
// TODO: handle exception
}
System.out.print(Thread.currentThread().getName() + ":" + list.get(i)+"\n");
list.remove(i);
}
}
}
複製程式碼
16、Java中物件的生命週期
-
1、建立階段(Created)
- 1 為物件分配儲存空間
- 2 開始構造物件
- 3 從超類到子類對static成員進行初始化
- 4 超類成員變數按順序初始化,遞迴呼叫超類的構造方法
- 5 子類成員變數按順序初始化,子類構造方法呼叫
-
2、應用階段(In Use)
- 物件至少被一個強引用持有著
-
3、不可見階段(Invisible)
- 說明程式本身不再持有該物件的任何強引用,雖然該這些引用仍然是存在著的。
publec int getNum(){
int num =0;
reture num+1;
}
//不可見
Log.e("error--->",num);
複製程式碼
-
4、不可達階段(Unreachable)
- 物件處於不可達階段是指該物件不再被任何強引用所持有。
-
5、收集階段(Collected)
- 當垃圾回收器發現該物件已經處於“不可達階段”並且垃圾回收器已經對該物件的記憶體空間重新分配做好準備時,則物件進入了“收集階段”。
-
6、終結階段
- 當物件執行完finalize()方法後仍然處於不可達狀態時,則該物件進入終結階段。在該階段是等待垃圾回收器對該物件空間進行回收。
-
7、物件空間重新分配階段
- 垃圾回收器對該物件的所佔用的記憶體空間進行回收或者再分配了,則該物件徹底消失了,稱之為“物件空間重新分配階段”。
17、Synchronized用法
- 1)synchronized是Java中的關鍵字,是一種同步鎖。它修飾的物件有以下幾種:
- 1.修飾一個程式碼塊,被修飾的程式碼塊稱為同步語句塊,其作用的範圍是大括號{}括起來的程式碼,作用的物件是呼叫這個程式碼塊的物件;
- 2.修飾一個方法,被修飾的方法稱為同步方法,其作用的範圍是整個方法,作用的物件是呼叫這個方法的物件;
- 3.修改一個靜態的方法,其作用的範圍是整個靜態方法,作用的物件是這個類的所有物件;
- 4.修改一個類,其作用的範圍是synchronized後面括號括起來的部分,作用主的物件是這個類的所有物件。
- 2)總結:
- 1.無論synchronized關鍵字加在方法上還是物件上,如果它作用的物件是非靜態的,則它取得的鎖是物件;如果synchronized作用的物件是一個靜態方法或一個類,則它取得的鎖是對類,該類所有的物件同一把鎖。
- 2.每個物件只有一個鎖(lock)與之相關聯,誰拿到這個鎖誰就可以執行它所控制的那段程式碼。
- 3.實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以儘量避免無謂的同步控制。
注意:作用於一個類,和作用於一個物件的區別!!!
18、synchronize的原理
- 1)通過反編譯synchronize程式碼塊可以得到 monitorenter 和 monitorexit:
- 1.monitorenter:每個物件都有一個監視器鎖(monitor),當monitor被佔用時就會處於鎖定狀態,執行緒中執行monitorenter就是嘗試獲取monitor的所有權。其過程如下:
- a.如果moniter的進入數為0,則該執行緒進入monitor。設定monitor的進入數為1,既該執行緒成為monitor的所有者。
- b.如果該執行緒已佔有該monitor,則只需重新進入,monitor的進入數加1。
- c.如果其他執行緒佔有了該monitor,則該執行緒進入阻塞狀態。直到monitor的進入數為0.再嘗試獲取monitor的所有權
- 2.monitorexit:
- a.該指令執行時,monitor的進入數減一,當monitor的進入數為0時,該執行緒退出monitor,不再是執行緒的持有者。等著其他執行緒嘗試進入。
- 1.monitorenter:每個物件都有一個監視器鎖(monitor),當monitor被佔用時就會處於鎖定狀態,執行緒中執行monitorenter就是嘗試獲取monitor的所有權。其過程如下:
- 2)通過分析:synchronize的底層語義原理是通過monitor物件來完成的。
其實wait/notify等方法也依賴於monitor物件,這就是為什麼只有在同步的塊或者方法中才能呼叫wait/notify等方法,否則會丟擲java.lang.IllegalMonitorStateException的異常的原因。
19、談談對Synchronized關鍵字,類鎖,方法鎖,重入鎖的理解
- 1)Synchronized關鍵字:
- 2)類鎖:當Synchronized修飾某個程式碼塊的時候,那麼就程這段程式碼為同步程式碼塊
Synchronized(Object){ //注意Object被稱為鎖物件
........ //這裡面的就是同步的程式碼塊
}
複製程式碼
- 3)方法鎖:當Synchronized修飾某個方法的時候,那麼就稱這個方法為同步方法。
- 4)重入鎖:所謂重入鎖,指的是以執行緒為單位,當一個執行緒獲取物件鎖之後,這個執行緒可以再次獲取本物件上的鎖,而其他的執行緒是不可以的。所謂重入鎖,指的是以執行緒為單位,當一個執行緒獲取物件鎖之後,這個執行緒可以再次獲取本物件上的鎖,而其他的執行緒是不可以的。可重入鎖的意義在於防止死鎖
20、static synchronized 方法的多執行緒訪問和作用
- 1)Synchronized(例項鎖):鎖在每一個物件上。如果該類是一個單例(比如單例模式),那麼它就具有全域性鎖的概念。
- 2)Static Synchronized(全域性鎖):該鎖針對的是類的,無論例項化了多少個物件,那麼執行緒都是共享這個鎖的。
21、同一個類裡面兩個synchronized方法,兩個執行緒同時訪問的問題
- 1)在同一個類的一個例項中的兩個synchronized方法,併發訪問。
不可以!!!
多個執行緒訪問同一個類的synchronized方法時, 都是序列執行的!
synchronized方法使用了類java的內建鎖, 即鎖住的是方法所屬物件本身.
同一個鎖某個時刻只能被一個執行執行緒所獲取, 因此其他執行緒都得等待鎖的釋放.
複製程式碼
- 2)在同一個類的兩個例項中的兩個synchronized方法,併發訪問。
22、volatile的原理
- 1)volatile作用:
- 1.防止重排序,例項化一個物件其實可以分為三個步驟:
- a.分配記憶體空間
- b.初始化物件
- c.將記憶體空間地址賦值給相應的物件。
- ※ 作業系統可以對指令進行重排序,所以上面的過程也可能會變成如下過程:
- a.分配記憶體空間
- c.將記憶體空間地址賦值給相應的物件。
- b.初始化物件
- 1.防止重排序,例項化一個物件其實可以分為三個步驟:
如果是這個流程,多執行緒環境下就可能將一個未初始化的物件引用暴露出來,
從而導致不可預料的結果。
因此,為了防止這個過程的重排序,我們需要將變數設定為volatile型別的變數。
複製程式碼
- 2.實現可見性
可見性問題主要指一個執行緒修改了共享變數值,而另一個執行緒卻看不到。
引起可見性問題的主要原因是每個執行緒擁有自己的一個快取記憶體區——執行緒工作記憶體。
volatile關鍵字能有效的解決這個問題
- 3.保證原子性
volatile只能保證對單次讀/寫的原子性
複製程式碼
- 2)volatile原理:
23、談談volatile關鍵字的用法
- 同上
24、談談volatile關鍵字的作用
- Synchronized是一個比較重量級的操作,對系統的效能有比較大的影響,所以,如果有其他解決方案,我們通常都避免使用Synchronized來解決問題。而volatile關鍵字就是Java中提供的另一種解決可見性和有序性問題的方案。
25、談談NIO的理解
nio 是non-blocking的簡稱,NIO 的建立目的是為了讓 Java 程式設計師可以實現高速 I/O 而無需編寫自定義的本機程式碼。NIO最重要的三個組成部分:
- 1)通道 Channels
- 2)緩衝區 Buffers
- 3)選擇器 Selectors
26、synchronized 和volatile 關鍵字的區別
- 1)volatile是變數修飾符,而synchronized則作用於一段程式碼或方法。
- 2)volatile只是線上程記憶體和“主”記憶體間同步某個變數的值;而synchronized通過鎖定和解鎖某個監視器同步所有變數的值。顯然synchronized要比volatile消耗更多資源。
27、synchronized與Lock的區別
- 1)Lock是一個介面,而synchronized是Java中的關鍵字。
- 2)synchronized是在JVM層面上實現的,在程式碼執行時出現異常,JVM會自動釋放鎖定,使用Lock則不行,lock是通過程式碼實現的,要保證鎖定一定會被釋放,就必須將 unLock()放到finally{} 中;
- 3)Lock可以讓等待鎖的執行緒響應中斷,執行緒可以中斷去幹別的事務,而synchronized卻不行,使用synchronized時,等待的執行緒會一直等待下去,不能夠響應中斷;
- 4)通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。
- 5)Lock可以提高多個執行緒進行讀操作的效率。(可以通過readwritelock實現讀寫分離)
28、ReentrantLock 、synchronized和volatile比較
- 可重入鎖。可重入鎖是指同一個執行緒可以多次獲取同一把鎖。ReentrantLock和synchronized都是可重入鎖。
29、ReentrantLock的內部實現
- 1)ReentrantLock支援兩種獲取鎖的方式,一種是公平模型,一種是非公平模型
30、lock原理
31、死鎖的四個必要條件?
- 1)死鎖:是指兩個或兩個以上的程式在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。
- 2)條件:
- 1.互斥條件:一個資源每次只能被一個程式使用。
- 2.佔有且等待:一個程式因請求資源而阻塞時,對已獲得的資源保持不放。
- 3.不可強行佔有:程式已獲得的資源,在末使用完之前,不能強行剝奪。
- 4.迴圈等待條件:若干程式之間形成一種頭尾相接的迴圈等待資源關係。
32、怎麼避免死鎖?
- 1)避免一個執行緒獲取多個鎖。
- 2)避免一個執行緒在鎖內佔用多個資源,儘量保證每個鎖只佔用一個資源。
- 3)嘗試定時鎖,使用 lock.tryLock(timeout) 來替代使用內部索機制。
- 4)對於資料庫鎖,加鎖解鎖必須在一個資料庫連線裡,否則會出現解鎖失敗的情況。
33、物件鎖和類鎖是否會互相影響?
-
1)物件鎖:在程式碼中的方法上加了synchronized的鎖,或者synchronized(this)的程式碼段,java的所有物件都含有1個物件鎖,這個鎖由JVM自動獲取和釋放。
-
2)類鎖:在程式碼中的方法上加了static和synchronized的鎖,或者synchronized(xxx.class)的程式碼段
34、什麼是執行緒池,如何使用?
- 1)newSingleThreadExecutor:
單個執行緒的執行緒池,即執行緒池中每次只有一個執行緒工作,單執行緒序列執行任務 - 2)newFixedThreadExecutor(n)
固定數量的執行緒池,沒提交一個任務就是一個執行緒,直到達到執行緒池的最大數量,然後後面進入等待佇列直到前面的任務完成才繼續執行 - 3)newCacheThreadExecutor
可快取執行緒池,當執行緒池大小超過了處理任務所需的執行緒,那麼就會回收部分空閒(一般是60秒無執行)的執行緒,當有任務來時,又智慧的新增新執行緒來執行。 - 4)newScheduleThreadExecutor
大小無限制的執行緒池,支援定時和週期性的執行執行緒
35、Java的併發、多執行緒、執行緒模型
36、談談對多執行緒的理解
37、多執行緒有什麼要注意的問題?
38、談談你對併發程式設計的理解並舉例說明
39、談談你對多執行緒同步機制的理解?
40、如何保證多執行緒讀寫檔案的安全?
41、多執行緒斷點續傳原理
42、斷點續傳的實現