JAVA面試總結——多線程

多線程

1.如何實現多線程
(1)繼承Thread類,重寫run()方法


package study_java;

class MyThread extends Thread{//創建線程類
	public void run(){
		System.out.println("Thread body");//線程的函數體
	}
}

public class thread {

	public static void main(String[] args){
		MyThread myThread=new MyThread();
		myThread.start();//開啓線程
	}
	
}

值得注意的是,調用start()方法後並不是立即執行多線程代碼,而是使得該線程變爲可運行狀態(runnable),什麼時候運行多線程由操作系統決定。
(2)實現Runnable接口,實現run()方法


package study_java;

class Mythread2 implements Runnable{//創建線程類
	public void run(){
		System.out.println("Thread body");
	}
}


public class thread2 {

	public static void main(String[] args){
		Mythread2 mythread2=new Mythread2();
		Thread t=new Thread(mythread2);//參數實例化thread對象
		t.start();//開啓線程
	}
}

不管是繼承Thread類還是通過Runnable接口來實現多線程的方法,最終還是通過Thread對象的API來控制線程。
繼承Thread類和實現Runnable接口的區別?
java只能單繼承,因此如果是採用繼承Thread的方法,那麼以後進行代碼重構的時候可能會遇到問題,因爲你無法繼承別的類了。
其次如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runnable接口的話,很容易實現資源共享。舉例:例如讓有一個任務,要賣十張票。Thread類實現多線程就相當於給每個線程分配一個任務。而用runnable實現,相當於多個線程共同完成一個任務。
(3)實現Callable接口,重寫call()方法
Callable接口實際是屬於Executor框架中的功能類,Callable接口與Runnable接口的功能類似,但提供了比Runnable更強大的功能。主要表現爲:
①Callable可以在任務結束後提供一個返回值,Runnable無法提供這個功能。
②Callable中的call()方法可以拋出異常,而Runnable的run()方法不能拋出異常。
③運行Callable可以拿到一個Future對象,Future對象表示異步計算的結果,它提供了檢查計算是否完成的方法。由於線程屬於異步計算模型,因此無法從別的線程中得到函數的返回值,在這種情況下,就可以使用Future來監視目標線程調用call()方法的情況,當調用Future的get()方法以獲取結果時,當前線程就會阻塞,直到call()方法結束返回結果。
前兩種方式線程執行完都沒有返回值,只有最後一種是帶返回值的。
2.多線程同步的實現方法有哪些?
(1)synchronized關鍵字
synchronized關鍵字主要有兩種方法(synchronized方法和synchronized塊)
①synchronized方法,在方法的聲明前加入synchronized關鍵字,例如;

public synchronized void multiThreadAccess()

只有把需要被同步的資源的操作放到上面這個方法中,就能保證這個方法在同一時刻只能被一個線程訪問,從而保證了多線程訪問的安全性。
②synchronized塊。synchronized塊可以把任意的代碼段聲明爲synchronized,也可以指定上鎖的對象。

synchronized(syncObject){
//訪問synObject的代碼
}

(2)wait()方法與notify()方法
這兩個方法是在Object上定義的,也就是說所有對象都具有着兩個方法。當一個線程調用一個對象的wait()方法後,該線程進入阻塞狀態,直到這個對象的notify()方法別調用後,當前線程方可解除。這樣的好處在於協調兩個工作時可以更加靈活。
(3)lock
JDK1.5新增加了Lock接口以及它的一個實現類RenntrantLock(重入鎖),Lock也可以用來實現多線程的同步,具體而言,它提供瞭如下一些方法來實現多線程的同步:
①lock()
以阻塞的方式獲取鎖,也就是說,如果獲取到了鎖,立即返回;如果別的線程持有鎖,當前線程等待,直到獲取鎖後返回。
②tryLock()
以非阻塞的方式獲取鎖。只是嘗試性地去獲取一下鎖,如果獲取到鎖,立即返回true,否則,立即返回false。
③tryLock(long timeout, TimeUnit unit)
如果獲取了鎖,立即返回true,否則會等待參數給定地時間單元,在等待地過程中,如果獲取了鎖,就返回true,如果等待超時,返回false。
④lookInterruptibly()
如果獲取了鎖,立即返回;如果沒有獲取鎖,當前線程處於休眠狀態,直到獲得鎖,或者當前線程被別的線程中斷(會收到InterruptedExeption異常)。它與lock()方法最大的區別在於如果lock()方法獲取不到鎖,會一直處於阻塞狀態,且會忽略interrupt()方法。
3.sleep()方法與wait()方法有什麼區別
sleep()和wait()都是使線程暫執行一段時間的方法,sleep()方法與wait()方法區別主要如下:
(1)原理不同。sleep()方法是Thread類的靜態方法,是線程用來控制自身流程的,自己暫停執行,把執行機會讓給其他線程,計時時間一到,便會自動甦醒;wait()方法是Object類的方法,用於線程見的通信,這個方法會使當前擁有該對象鎖的進程等待,知道其他線程調用notify()方法才喚醒。和wait()配套的方法有notify()和notifyAll()方法。
(2)對鎖的處理機制不同。sleep()不涉及進程間的通信,調用sleep()方法並不會釋放鎖。而當調用wait()方法後,線程會釋放掉它所佔用的鎖,從而使線程所在對象中的其他synchronized數據可被別的線程調用。
(3)使用區域不一樣。sleep()方法可以放在任何地方使用。wait()方法必須放在同步控制方法或者同步語句塊中使用。
sleep()方法必須捕獲異常,而wait()、notify()、notifyAll()不需要捕獲異常。
4.sleep()方法與yield()方法有什麼區別?
Thread的靜態方法sleep→static void sleep(long ms)
可以使得當前線程進入阻塞狀態指定毫秒。當超時後,該線程會自動回到Runnable狀態,等待再次分配時間片運行。該方法聲明時會拋出一個InterruptException,所以在使用時需要捕獲這個異常。給其他線程執行機會時不考慮線程的優先級,因此也會給低優先級的線程運行的機會。

Thread的靜態方yield→static void yield()
該方法用於使當前線程主動讓出當次cpu時間片回到可執行狀態,等待時間片分配。(將一個線程的操作暫時讓給其他線程執行),只會給相同優先級或是跟高優先級線程以運行的機會。
5.終止線程的方法有哪些?
(1)stop():Thread.stop()來終止線程時,它會釋放已經鎖定的所有監視資源。
(2)suspend():當掛起有鎖的線程時,會導致程序發生死鎖。
由於以上兩種方法的不安全性,Java語言已經不建議使用以上兩種方式來終止線程了。一般建議是讓程序自行結束進入Dead狀態。
6.synchronized和Lock有什麼異同?
Java提供兩種鎖機制來實現對某個資源的同步,synchronized使用Object對象本身的notify、wait、notifyAll調度機制,Lock使用Condition進行線程之間的調度,完成synchronized實現的所有功能。
(1)用法不一樣
在需要同步的對象中加入synchronized控制,synchronized既可以加在方法上,也可以加在特定代碼塊中,括號中表示需要鎖的對象。而Lock需要顯式地指定起始位置和終止位置。synchronized是託管給JVM的,而Lock的鎖定是通過代碼實現的,具有更精確的線程語義。

(2)性能不一樣
在資源競爭不是很激烈的情況下,synchronized的性能要優於ReetrantLock,但是在資源競爭很激烈的情況下,synchronized的性能會下降得非常塊,而ReetrantLock的性能基本保持不變。

(3)鎖機制不一樣
synchronized獲得鎖和釋放的方式都是在塊結構中,當獲取多個鎖時,必須以相反的順序釋放,並且是自動解鎖,不會因爲出了異常而導致鎖沒有被釋放從而引發死鎖。而Lock則需要開發人員手動去釋放,並且必須在finally塊中釋放,否則會引起死鎖問題的發生。
7.什麼是守護線程
守護線程又稱“服務線程”,當所有非守護線程終止,只剩下守護線程,則進程運行終止。用戶線程設置守護線程的方法就是在調用start()犯法啓動線程之前調用對象的setDaemon(true)方法。
8.join()方法的作用是什麼
join()方法是讓調用該方法的線程在執行完run()方法後,再執行join方法後面的代碼。
9.線程池
(1)什麼是線程池?
線程池是一種多線程處理形式,處理過程中將任務提交到線程池,任務的執行交由線程池來管理。
如果每個請求都創建一個線程去處理,那麼服務器的資源很快就會被耗盡,使用線程池可以減少創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個任務。
(2)爲什麼使用線程池?

  • 創建線程和銷燬線程的花銷是比較大的,這些時間有可能比處理業務的時間還要長。這樣頻繁的創建線程和銷燬線程,再加上業務工作線程,消耗系統資源的時間,可能導致系統資源不足。(我們可以把創建和銷燬的線程的過程去掉)
  • 可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因爲消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。

(3)線程池有什麼作用?

線程池作用就是限制系統中執行線程的數量。
根據系統的環境情況,可以自動或手動設置線程數量,達到運行的最佳效果;少了浪費了系統資源,多了造成系統擁擠效率不高。用線程池控制線程數量,其他線程排隊等候。一個任務執行完畢,再從隊列的中取最前面的任務開始執行。若隊列中沒有等待進程,線程池的這一資源處於等待。當一個新任務需要運行時,如果線程池中有等待的工作線程,就可以開始運行了;否則進入等待隊列。
(4)幾種常見的線程池以及使用場
①newSingleThreadExecutor(創建一個單線程的線程池)
這個線程池只有一個線程在工作,相當於單線程串行執行所有任務。此線程池保證所有任務的執行順序按照任務的提交順序執行。如果這唯一的線程因爲異常結束,那麼會有一個新的線程來替代它。用於需要保證順序執行的場景,並且只有一個線程在執行。
②newFixedThreadPool(創建固定大小的線程池)
每提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因爲執行異常而結束,那麼線程池會補充一個新線程。用於已知併發壓力的情況下,對線程數做限制。
newCachedThreadPool(創建一個可緩存的線程池)
如果線程池的大小超過了處理任務所需要的線程,那麼就會回收部分空閒的線程。當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。比較適合處理執行時間比較小的任務。
④newSchedueledThreadPool(創建一個大小無限的線程池)
此線程支持定時以及週期性執行任務的需求。適用於需要多個後臺線程執行週期任務的場景。
(5)線程池的關閉
關閉線程池可以調用shutdownNows和shutdown兩個方法來實現
**shutdownNow:**對正在執行的任務全部發出interrupt()停止執行,對還未開始執行的任務全部取消,並且返回還沒開始的任務列表。
**shutdown:**線程池將不再接受新的任務,但也不會去強制終止已經提交或者正在執行中的任務。
10.併發
從宏觀方面來說,併發是同時戶必須辦法多種時間,實際上這幾種時間並不是同時進行的,而是交替進行的。而由於cpu運算速度非常快,會造成我們的錯覺,就是在同一時間進行很多事情。
**微觀方面來說,**所有併發處理等待喚醒執行,先後鍵入隊列排隊等候 執行。
併發的實質是一個物理cpu在若干道程序(或線程)之間多路複用,併發性是對有限物理資源強制行使多個用戶共享以提高效率。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章