1. 程式人生 > >JAVA筆試面試題系列之----①多執行緒

JAVA筆試面試題系列之----①多執行緒

1.      程序和執行緒:

程序:正在進行的程式。每一個程序執行都有一個執行順序,該順序是一個執行路徑,或者叫一個控制單元。

執行緒:程序內部的一條執行路徑或者一個控制單元。

兩者的區別:

一個程序至少有一個執行緒

程序在執行過程中擁有獨立的記憶體單元,而多個執行緒共享記憶體;

2.      jvm多執行緒的啟動是多執行緒嗎?

java的虛擬機器jvm啟動的是單執行緒,就有發生記憶體洩露的可能,而我們使用java程式沒出現這樣的問題,

也就是jvm啟動至少有兩個執行緒,一個執行java程式,一個執行垃圾回收。所以是多執行緒。

3.      多執行緒的優勢:

解決了多部分同時執行的問題,提高效率

4.      執行緒的弊端:

執行緒太多會導致效率的降低,因為執行緒的執行依靠的是CPU的來回切換。

5.      什麼叫多執行緒:

一個程序中有多個執行緒,稱為多執行緒。

6.      實現多執行緒的方法:

實現多執行緒可以通過繼承Thread類和實現Runnable介面。

(1)繼承Thread

定義一個類繼承Thread

複寫Thread類中的public void run()方法,將執行緒的任務程式碼封裝到run方法中

直接建立Thread的子類物件,建立執行緒

呼叫start()方法,開啟執行緒(呼叫執行緒的任務run方法)

   //另外可以通過ThreadgetName()

獲取執行緒的名稱。

(2)實現Runnable介面;

定義一個類,實現Runnable介面;

覆蓋介面的public void run()的方法,將執行緒的任務程式碼封裝到run方法中;

建立Runnable介面的子類物件

Runnabl介面的子類物件作為引數傳遞給Thread類的建構函式,建立Thread類物件

(原因:執行緒的任務都封裝在Runnable介面子類物件的run方法中。所以要線上程物件建立時就必須明確要執行的任務)。

呼叫start()方法,啟動執行緒。

兩種方法區別:

(1)實現Runnable介面避免了單繼承的侷限性

(2)繼承Thread類執行緒程式碼存放在Thread

子類的run方法中

實現Runnable介面執行緒程式碼存放在介面的子類的run方法中;

在定義執行緒時,建議使用實現Runnable介面,因為幾乎所有多執行緒都可以使用這種方式實現

7.      建立執行緒是為什麼要複寫run方法?

Thread類用於描述執行緒。Thread類定義了一個功能,用於儲存執行緒要執行的程式碼,該儲存功能就是run方法。

8.      start()和run方法有什麼區別?

呼叫start方法方可啟動執行緒,而run方法只是thread的一個普通方法,呼叫run方法不能實現多執行緒;

Start()方法:

start方法用來啟動執行緒,實現了多執行緒執行,這時無需等待run方法體程式碼執行完畢而直接繼續執行下面的

程式碼。通過呼叫Thread類的start()方法來啟動一個執行緒,這時此執行緒處於就緒(可執行)狀態,並沒有執行,

一旦得到cpu時間片(執行權),就開始執行run()方法,這裡方法run()稱為執行緒體,

它包含了要執行的這個執行緒的內容,Run方法執行結束,此執行緒隨即終止。

Run()方法:

run()方法只是Thread類的一個普通方法,如果直接呼叫Run方法,程式中依然只有主執行緒這一個執行緒,

其程式執行路徑還是隻有一條,還是要等待run方法體執行完畢後才可繼續執行下面的程式碼,

這樣就沒有達到多執行緒的目的。

9.      執行緒的幾種狀態:

新建:new一個Thread物件或者其子類物件就是建立一個執行緒,當一個執行緒物件被建立,但是沒有開啟,這個時候,只是物件執行緒物件開闢了記憶體空間和初始化資料。

就緒:新建的物件呼叫start方法,就開啟了執行緒,執行緒就到了就緒狀態。在這個狀態的執行緒物件,具有執行資格,沒有執行權。

執行:當執行緒物件獲取到了CPU的資源。在這個狀態的執行緒物件,既有執行資格,也有執行權。

凍結:執行過程中的執行緒由於某些原因(比如wait,sleep),釋放了執行資格和執行權。當然,他們可以回到執行狀態。只不過,不是直接回到。而是先回到就緒狀態。

死亡:當執行緒物件呼叫的run方法結束,或者直接呼叫stop方法,就讓執行緒物件死亡,在記憶體中變成了垃圾。

10.  sleep()和wait()的區別:

(1)這兩個方法來自不同的類,sleep()來自Thread類,和wait()來自Object類。

(2)sleepThread的靜態類方法,誰呼叫的誰去睡覺,即使在a執行緒裡呼叫了bsleep方法,實際上還是a去睡覺,

要讓b執行緒睡覺要在b的程式碼中呼叫sleep。而wait()Object類的非靜態方法

(3)sleep()釋放資源不釋放鎖,而wait()釋放資源釋放鎖;

(4)使用範圍:wait,notifynotifyAll只能在同步控制方法或者同步控制塊裡面使用,sleep可以在任何地方使用

11.  多執行緒安全問題:

(1)原因:當程式的多條語句在操作執行緒共享資料時(如買票例子中的票就是共享資源),由於執行緒的隨機性導致

一個執行緒對多條語句,執行了一部分還沒執行完,另一個執行緒搶奪到cpu執行權參與進來執行,

此時就導致共享資料發生錯誤。比如買票例子中列印重票和錯票的情況。

(2)解決方法:對多條操作共享資料的語句進行同步,一個執行緒在執行過程中其他執行緒不可以參與進來

12.  Java中多執行緒同步是什麼?

同步是用來解決多執行緒的安全問題的,在多執行緒中,同步能控制對共享資料的訪問。如果沒有同步,當一個執行緒在

修改一個共享資料時,而另外一個執行緒正在使用或者更新同一個共享資料,這樣容易導致程式出現錯誤的結果。

13.  什麼是鎖?鎖的作用是什麼?

鎖就是物件

鎖的作用是保證執行緒同步,解決執行緒安全問題。

持有鎖的執行緒可以在同步中執行,沒有鎖的執行緒即使獲得cpu執行權,也進不去。

14.  同步的前提:

(1)必須保證有兩個以上執行緒

(2)必須是多個執行緒使用同一個鎖,即多條語句在操作執行緒共享資料

(3)必須保證同步中只有一個執行緒在執行

15.  同步的好處和弊端

好處:同步解決了多執行緒的安全問題

弊端:多執行緒都需要判斷鎖,比較消耗資源

16.  同步的兩種表現形式:

(1)同步程式碼塊:

可以指定需要獲取哪個物件的同步鎖,使用synchronized的程式碼塊同樣需要鎖,但他的鎖可以是任意物件

考慮到安全問題,一般還是使用同一個物件,相對來說效率較高。

注意:

**雖然同步程式碼快的鎖可以使任何物件,但是在進行多執行緒通訊使用同步程式碼快時,

必須保證同步程式碼快的鎖的物件和,否則會報錯。

**同步函式的鎖是this,也要保證同步函式的鎖的物件和呼叫waitnotifynotifyAll的物件是

同一個物件,也就是都是this鎖代表的物件。

格式:

synchronized(物件)

{

需同步的程式碼;

}

(2)同步函式

同步方法是指進入該方法時需要獲取this物件的同步鎖,在方法上使用synchronized關鍵字,

使用this物件作為鎖,也就是使用了當前物件,因為鎖住了方法,所以相對於程式碼塊來說效率相對較低。

:靜態同步函式的鎖是該方法所在的類的位元組碼檔案物件,即類名.class檔案

格式:

修飾詞 synchronized 返回值型別函式名(引數列表)

{

需同步的程式碼;

}

jdk1.5後,用lock鎖取代了synchronized,個人理解也就是對同步程式碼塊做了修改,

並沒有提供對同步方法的修改,主要還是效率問題吧。

17.  多執行緒的單例設計模式:保證某個類中記憶體中只有一個物件

(1)餓漢式:

                                      classSingle

                                      {

                                                     privateSingle(){}

//將建構函式私有化,不讓別的類建立該類物件

                                                     privatestatic final Single s=new Single();

//自己建立一個物件

                                                     publicstatic Single getInstance()

//提供一個公共訪問方式

                                                     {

                                                                    returns;

                                                     }

                                      }

(2)懶漢式:

                                      classSingle

                                      {

                                                     privateSingle(){} 

                                                     privatestatic Single s;

                                                     publicstatic Single getInstance()

                                                     {

                                                                    if(s==null)

                                                                                   s=newSingle();

                                                                    returns;

                                                     }

                                      }

餓漢式和懶漢式的區別:

**

餓漢式是類一載入進記憶體就建立好了物件;

懶漢式則是類載入進記憶體的時候,物件還沒有存在,只有呼叫了getInstance()方法時,物件才開始建立。

**

懶漢式是延遲載入,如果多個執行緒同時操作懶漢式時就有可能出現執行緒安全問題,解決執行緒安全問題

可以加同步來解決。但是加了同步之後,每一次都要比較鎖,效率就變慢了,

所以可以加雙重判斷來提高程式效率。

如將上述懶漢式的Instance函式改成同步:

                                      publicstatic Single getInstance()

                                      {

                                                     if(s==null)

                                                     {

                                                                    synchronized(Single.class)

                                                                    {

                                                                                   if(s==null) 

                                                                                                   s=newSingle();

                                                                    }

                                                     }

                                                     returns;

                                      }

18.  死鎖

兩個執行緒對兩個同步物件具有迴圈依賴時,就會發生死鎖。即同步巢狀同步,而鎖卻不同。

19.  wait()、sleep()、notify()、notifyAll()

wait():使一個執行緒處於等待狀態,並且釋放所持有的物件的lock

sleep():使一個正在執行的執行緒處於睡眠狀態,是一個靜態方法,呼叫此方法要捕捉InterruptedException異常。

notify():喚醒一個處於等待狀態的執行緒,注意的是在呼叫此方法的時候,並不能確切的喚醒某一個等待狀態的執行緒,

而是由JVM確定喚醒哪個執行緒(一般是最先開始等待的執行緒),而且不是按優先順序。

Allnotity():喚醒所有處入等待狀態的執行緒,注意並不是給所有喚醒執行緒一個物件的鎖,而是讓它們競爭。

20.  為什麼wait()、notify()、notifyAll()這些用來操作執行緒的方法定義在Object類中?

(1)這些方法只存在於同步中;

(2)使用這些方法時必須要指定所屬的鎖,即被哪個鎖呼叫這些方法;

(3)而鎖可以是任意物件,所以任意物件呼叫的方法就定義在Object中。

21.  多執行緒間通訊:

多執行緒間通訊就是多個執行緒在操作同一資源,但是操作的動作不同.

       (1)為什麼要通訊

多執行緒併發執行的時候, 如果需要指定執行緒等待或者喚醒指定執行緒, 那麼就需要通訊.比如生產者消費者的問題,

生產一個消費一個,生產的時候需要負責消費的程序等待,生產一個後完成後需要喚醒負責消費的執行緒,

同時讓自己處於等待,消費的時候負責消費的執行緒被喚醒,消費完生產的產品後又將等待的生產執行緒喚醒,

然後使自己執行緒處於等待。這樣來回通訊,以達到生產一個消費一個的目的。

       (2)怎麼通訊

在同步程式碼塊中, 使用鎖物件的wait()方法可以讓當前執行緒等待, 直到有其他執行緒喚醒為止.

使用鎖物件的notify()方法可以喚醒一個等待的執行緒,或者notifyAll喚醒所有等待的執行緒.

多執行緒間通訊用sleep很難實現,睡眠時間很難把握。

22.  Lock和Condition

實現提供比synchronized方法和語句可獲得的更廣泛的鎖的操作,可支援多個相關的Condition物件

Lock是個介面

鎖是控制多個執行緒對共享資料進行訪問的工具。

JDK1.5中提供了多執行緒升級的解決方案:

將同步synchonized替換成了顯示的Lock操作,將Object中的waitnotifynotifyAll替換成了Condition物件。

該物件可以Lock鎖進行獲取

Lock的方法摘要:

void lock() 獲取鎖。

ConditionnewCondition() 返回繫結到此 Lock 例項的新 Condition 例項。

void unlock() 釋放鎖。

Condition方法摘要:

void await() 造成當前執行緒在接到訊號或被中斷之前一直處於等待狀態。

void signal() 喚醒一個等待執行緒。

void signalAll() 喚醒所有等待執行緒。

23.  停止執行緒:

stop方法已經過時,如何停止執行緒?

停止執行緒的方法只有一種,就是run方法結束。如何讓run方法結束呢?

開啟多執行緒執行,執行程式碼通常是迴圈體,只要控制住迴圈,就可以讓run方法結束,也就是結束執行緒。

特殊情況:當執行緒屬於凍結狀態,就不會讀取迴圈控制標記,則執行緒就不會結束。

為解決該特殊情況,可引入Thread類中的Interrupt方法結束執行緒的凍結狀態;

當沒有指定的方式讓凍結執行緒恢復到執行狀態時,需要對凍結進行清除,強制讓執行緒恢復到執行狀態

24.  interrupt:

void interrupt() 中斷執行緒

中斷狀態將被清除,它還將收到一個 InterruptedException

25.  守護執行緒(後臺執行緒)

setDaemon(boolean on):將該執行緒標記為守護執行緒或者使用者執行緒。

當主執行緒結束,守護執行緒自動結束,比如聖鬥士星矢裡面的守護雅典娜,

在多執行緒裡面主執行緒就是雅典娜,守護執行緒就是聖鬥士,主執行緒結束了,

守護執行緒則自動結束。

當正在執行的執行緒都是守護執行緒時,java虛擬機器jvm退出;所以該方法必須在啟動執行緒前呼叫;

守護執行緒的特點:

守護執行緒開啟後和前臺執行緒共同搶奪cpu的執行權,開啟、執行兩者都沒區別,

但結束時有區別,當所有前臺執行緒都結束後,守護執行緒會自動結束。

26.  多執行緒join方法:

void join() 等待該執行緒終止。

void join(longmillis)  等待該執行緒終止的時間最長為 millis 毫秒。

throwsInterruptedException         

特點:當A執行緒執行到B執行緒的join方法時,A就會等待B執行緒都執行完,A才會執行

作用: join可以用來臨時加入執行緒執行;

27.  多執行緒優先順序:yield()方法

yield():暫停當前正在執行的執行緒物件,並執行其他執行緒

setPriority(intnewPriority):更改執行緒優先順序

int getPriority()返回執行緒的優先順序。

String toString()返回該執行緒的字串表示形式,包括執行緒名稱、優先順序和執行緒組

(1)MAX_PRIORITY:最高優先順序(10)

(1)Min_PRIORITY:最低優先順序(1)

(1)Morm_PRIORITY:預設優先順序(5)

28.  什麼是ThreadLocal類,怎麼使用它?

ThreadLocal類提供了執行緒區域性 (thread-local) 變數。是一個執行緒級別的區域性變數,並非本地執行緒

ThreadLocal 為每個使用該變數的執行緒,提供了一個獨立的變數副本,每個執行緒修改副本時不影響其它執行緒物件的副本

下面是執行緒區域性變數(ThreadLocal variables)的關鍵點:

一個執行緒區域性變數(ThreadLocal variables)為每個執行緒方便地提供了一個單獨的變數。

ThreadLocal 例項通常作為靜態的私有的(private static)欄位出現在一個類中,這個類用來關聯一個執行緒。

當多個執行緒訪問 ThreadLocal 例項時,每個執行緒維護 ThreadLocal 提供的獨立的變數副本。

常用的使用可在 DAO 模式中見到,當 DAO 類作為一個單例類時,

資料庫連結(connection)被每一個執行緒獨立的維護,互不影響。(基於執行緒的單例)

29.  什麼時候丟擲InvalidMonitorStateException異常?為什麼?

呼叫 wait ()/notify ()/notifyAll ()中的任何一個方法時,如果當前執行緒沒有獲得該物件的鎖,

那麼就會丟擲 IllegalMonitorStateException 的異常

也就是說程式在沒有執行物件的任何同步塊或者同步方法時,

仍然嘗試呼叫 wait ()/notify ()/notifyAll ()時。由於該異常是 RuntimeExcpetion 的子類,

所以該異常不一定要捕獲(儘管你可以捕獲只要你願意

作為 RuntimeException,此類異常不會在 wait (),notify (),notifyAll ()的方法簽名提及。

30.  在靜態方法上使用同步時會發生什麼事?

同步靜態方法時會獲取該類的“Class”物件,所以當一個執行緒進入同步的靜態方法中時,

執行緒監視器獲取類本身的物件鎖,其它執行緒不能進入這個類的任何靜態同步方法。

它不像例項方法,因為多個執行緒可以同時訪問不同例項同步例項方法。

31.  當一個同步方法已經執行,執行緒能夠呼叫物件上的非同步例項方法嗎?

可以,一個非同步方法總是可以被呼叫而不會有任何問題。

實際上,Java 沒有為非同步方法做任何檢查,鎖物件僅僅在同步方法或者同步程式碼塊中檢查。

如果一個方法沒有宣告為同步,即使你在使用共享資料Java照樣會呼叫,而不會做檢查是否安全,

所以在這種情況下要特別小心。一個方法是否宣告為同步取決於臨界區訪問(critial section access)

如果方法不訪問臨界區(共享資源或者資料結構)就沒必要宣告為同步的。

32.  在一個物件上兩個執行緒可以呼叫兩個不同的同步例項方法嗎?

不能,因為一個物件已經同步了例項方法,執行緒獲取了物件的物件鎖。

所以只有執行完該方法釋放物件鎖後才能執行其它同步方法。

33.  什麼是執行緒餓死,什麼是活鎖?

執行緒餓死和活鎖雖然不像死鎖一樣是常見的問題,但是對於併發程式設計的設計者來說就像一次邂逅一樣。

當所有執行緒阻塞,或者由於需要的資源無效而不能處理,不存在非阻塞執行緒使資源可用。

JavaAPI 中執行緒活鎖可能發生在以下情形:

當所有執行緒在程式中執行 Object.wait (0),引數為 0 wait 方法。

程式將發生活鎖直到在相應的物件上有執行緒呼叫 Object.notify ()或者Object.notifyAll ()

當所有執行緒卡在無限迴圈中。