線程問題彙總

一 線程狀態轉換圖

注意:調用obj.wait()的線程須要先獲取obj的monitor,wait()會釋放obj的monitor並進入等待態。因此wait()/notify()都要與synchronized聯用java

 

二 阻塞和等待的區別

阻塞: 當一個線程試圖得到對象鎖(非juc庫中的鎖,即synchronized),而該鎖被其餘線程持有,則該線程進入阻塞狀態。它的特色是使用簡單,由jvm調度器來決定喚醒本身,而不是須要由另外一個線程來顯式喚醒本身,不響應中斷api

等待: 當一個線程等待另外一個線程通知調度器一個條件時,該線程進入等待狀態。它的特色是須要等待另外一個線程顯式地喚醒本身,實現靈活,語意更豐富,可響應中斷。例如: Object.wait(), Thread.join() 以及等待lock或condition安全

須要強調的是雖然synchronized和JUC裏的Lock都實現鎖的功能,但線程進入的狀態是不同的。synchronized會讓線程進入阻塞態,而JUC裏的lock是用LockSupport.park()/unpark()來實現阻塞/喚醒的,會讓線程進入等待態。但事實上,雖然等待鎖時進入的狀態不同,但被喚醒後又都進入runnable態,從行爲效果來看又是同樣的。多線程

 

三 幾個方法的使用和坑

1 sleep

sleep至關於讓線程睡眠,交出cpu,讓cpu去執行其餘的任務。可是sleep不會釋放鎖,也就是說若是當前線程持有對某個對象的鎖,則即便調用sleep方法,其餘線程也沒法訪問這個對象。框架

2 yield

調用yield方法會讓當前線程交出cpu權限,讓cpu去執行其餘的線程。它跟sleep方法類型,一樣不會釋放鎖。可是jield不能控制具體的交出cpu的時間。jvm

注意調用yield方法並不會讓線程進入阻塞狀態,而是讓線程重回就緒狀態,它只須要等待從新得到Cpu執行時間。ide

3 join

join有三個重載版本函數

1 join()
2 join(long millis)     //參數爲毫秒
3 join(long millis,int nanoseconds)    //第一參數爲毫秒,第二個參數爲納秒

join()實際是利用了wait(),只不過它不用等待notify()/notifyAll(),且不受其影響。它結束的條件是:1)等待時間到;2)目標線程已經run完(經過isAlive()來判斷)。this

join和synchronized的區別是: join在內部使用wait()方法進行等待,而synchronized關鍵字使用的是"對象監視器"做爲同步spa

public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    
    //0則須要一直等到目標線程run完
    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        //若是目標線程未run完且阻塞時間未到,那麼調用線程會一直等待。
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}
View Code

4 interrupt

此操做會中斷等待中的線程,並將線程的中斷標誌位置位。若是線程在運行態則不會受此影響

能夠經過如下三種方式來判斷中斷:

1)isInterrupted()

此方法只會讀取線程的中斷標誌位,並不會重置。

2)interrupted()

此方法讀取線程的中斷標誌位,並會重置。

3)throw InterruptException

拋出該異常的同時,會重置中斷標誌位。

5 suspend/resume

掛起線程,直到被resume,纔會甦醒

但調用suspend()的線程和調用resume()的線程,可能會由於爭鎖的問題而發生死鎖,因此JDK 7開始已經不推薦使用了。

 

 

四 相關知識彙總

1 什麼是線程,和進程的區別

線程是操做系統可以進行運算調度的最小單位,它被包含在進程之中,是進程中實際運做單位。

線程是進程的子集,一個進程能夠有不少線程,每條線程並行執行不一樣的任務。不一樣的進程使用不一樣的內存空間,而全部的線程共享一片相同的內存空間。別把它和棧內存搞混,每一個線程都擁有單獨的棧內存用來存儲本地數據。

 

2 java如何中止一個線程

Java提供了很豐富的API但沒有爲中止線程提供API。JDK 1.0原本有一些像stop(), suspend() 和 resume()的控制方法。可是因爲潛在的死鎖威脅,在後續的JDK版本中他們被棄用了,以後Java API的設計者就沒有提供一個兼容且線程安全的方法來中止一個線程。當run() 或者 call() 方法執行完的時候線程會自動結束,若是要手動結束一個線程,你能夠用volatile 布爾變量來退出run()方法的循環或者是取消任務來中斷線程。

    private class Runner extends Thread{
    volatile boolean bExit = false;
  
    public void exit(boolean bExit){
        this.bExit = bExit;
    }
  
    @Override
    public void run(){
        while(!bExit){
            System.out.println("Thread is running");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException ex) {
                    Logger.getLogger(ThreadTester.class.getName()).log(Level.SEVERE, null, ex);
                }
        }
    }
}

 

3 爲何Thread類的sleep()和yield()方法是靜態的

Thread類的sleep和yield都是做用在當前正在執行的線程上運行,因此其餘處於等待狀態的線程上調用這些方法是沒有意義的。設置爲靜態代表在當前執行的線程上工做,避免開發錯誤地認爲能夠在其餘非運行線程調用這些方法。

 

4 在java中wait和sleep方法的不一樣

最大的不一樣是: 在等待時wait會釋放鎖,而sleep一直持有鎖。wait一般用於線程間交互,sleep一般被用於暫停執行。

 

5 線程的優先級

Java中線程的優先級分爲1-10這10個等級,若是小於1或大於10則JDK拋出IllegalArgumentException()的異常,默認優先級是5。在Java中線程的優先級具備繼承性,好比A線程啓動B線程,則B線程的優先級與A是同樣的。注意程序正確性不能依賴線程的優先級高低,由於操做系統能夠徹底不理會Java線程對於優先級的決定。

 

6 爲何wait和notify方法要在同步塊中調用

java api強制要求這麼作,不然會拋出IllegalMonitorStateException異常。還有一個緣由是爲了不wait和notify之間產生競態條件。

 

7 java線程池中submit()和execute()方法有什麼區別

二者均可以向線程池提交任務,execute()方法的返回類型是void, 它定義在Executor接口中,而submit()方法能夠返回有計算結果獲得Future對象,它定義在ExecutorService接口中,它擴展了Executor接口。

 

8 java中Runnable和Callable有什麼不一樣

二者都表明那些要在不一樣的線程中執行的任務。Runnable從jdk1.0就開始有了,Callable是在jdk1.5增長的。它們的主要區別是Callable的call()方法能夠返回值和拋出異常,而Runnable的run()沒有這些功能。Callable能夠裝載有計算結果的Future對象。

 

9 爲何wait, notify, notifyAll這些方法不在thread類裏面

主要是由於java提供的鎖是對象級的而不是線程級的,每一個對象都有鎖,經過線程得到。wait,notify和notifyAll都是鎖級別的操做,因此把它們定義在Object類中由於鎖屬於對象。

 

10 守護進程

Java中有兩種線程,一種是用戶線程,另外一種是守護線程。當進程中不存在非守護線程了,則守護線程自動銷燬。經過setDaemon(true)設置線程爲後臺線程。注意thread.setDaemon(true)必須在thread.start()以前設置,不然會報IllegalThreadStateException異常;在Daemon線程中產生的新線程也是Daemon的;在使用ExecutorService等多線程框架時,會把守護線程轉換爲用戶線程,而且也會把優先級設置爲Thread.NORM_PRIORITY。在構建Daemon線程時,不能依靠finally塊中的內容來確保執行關閉或清理資源的邏輯

 

11 wait, notify, notifyAll用法

首先要明確,只能在synchronized同步方法或者同步代碼塊中使用這些。在執行wait方法後,當前線程釋放鎖(這點與sleep, yield不一樣)。調用了wait函數的線程會一直等待,直到有其餘線程調用了同一個對象的notify或notifyAll方法。須要注意的是,被喚醒並不表明馬上得到對象的鎖,要等待執行notify方法的線程執行完,也即退出synchronized代碼塊後,當前線程纔會釋放鎖,進而wait狀態的線程才能夠得到該對象鎖

不在同步代碼塊會有IllegalMonitorStateException異常(RuntimeException)

* @throws  IllegalMonitorStateException  if the current thread is not
* the owner of the object's monitor.

notify方法只會(隨機)喚醒一個正在等待的線程,而notifyAll方法會喚醒全部正在等待的線程。若是一個對象以前沒有調用wait方法,那麼調用notify方法是沒有任何影響的

 

12 interrupted和isInterrupted的區別

interrupted   判斷當時線程是否已是中斷狀態,執行後清除狀態標誌

isInterrupted 判斷當時線程是否已是中斷狀態,執行後清除狀態標誌

public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}
public boolean isInterrupted() {
    return isInterrupted(false);
}
private native boolean isInterrupted(boolean ClearInterrupted);
相關文章
相關標籤/搜索