1. 程式人生 > >java基礎知識-面試(三)

java基礎知識-面試(三)

執行緒

  • 建立執行緒有幾種不同的方式
    java建立執行緒有三種方式:
    1.繼承Thread類建立執行緒類
    2.通過Runnable介面建立執行緒類
    3.通過Callable和FutureTask建立執行緒,支援返回值,封裝在FutureTask中

這裡寫圖片描述

這裡寫圖片描述

實現Runnable和Callable介面的方式基本相同,不過是後者執行call方法有返回值
1.如果要訪問當前執行緒,必須呼叫Thread.currentThread()方法
2.繼承Thread類的執行緒類不能再繼承其它父類(java的單繼承決定)

注:一般推薦採用實現介面的方式來建立多執行緒

執行緒的生命週期介紹下?
作業系統中執行緒和程序的概念?
現在的作業系統是多工作業系統,多執行緒是實現多工的一種方式
程序是指一個記憶體中執行的應用程式,每個程序都有自己獨立的記憶體空間,一個程序中可以啟動多個執行緒,比如在windows中,一個.exe就是一個程序

執行緒指的是程序中的一個執行流程,一個程序中可以執行多個執行緒,比如java.exe程序中可以執行很多執行緒,執行緒總是屬於某個程序,程序中的多個執行緒共享程序的記憶體

同時“執行”是人的感覺,實際上執行緒之間存在輪換執行
多執行緒能滿足程式設計師高效的編寫來達到充分利用CPU的目的

2.執行緒的生命週期
執行緒是一個動態執行的過程,它也有一個從建立到死亡的過程

新建狀態:執行start方法
就緒狀態:執行run方法,系統排程,獲取CPU資源
執行狀態:run方法執行完成
死亡狀態:可用stop和destory函式強制終止
阻塞狀態:執行緒阻塞,讓出CPU資源

新建狀態:使用new關鍵字和Thread類或者其子類建立一個執行緒物件後,該執行緒就屬於新建狀態,它保持這個狀態到start()這個執行緒

就緒狀態:當執行緒物件呼叫了start方法之後,該執行緒就進入就緒狀態,就緒狀態的執行緒處於就緒佇列之中,要等待jvm裡執行緒排程器的排程

執行狀態:如果就緒狀態的執行緒獲取CPU資源,就可執行run(),此時的執行緒便處於執行狀態,處於執行狀態的執行緒最為複雜,它可以變為阻塞狀態,就緒狀態和死亡狀態
阻塞狀態:如果一個執行緒執行了sleep(睡眠),suspend(掛起)方法,失去佔用的資源後,該執行緒就從執行狀態進入阻塞狀態,在睡眠時已獲得裝置資源後就可以重新進入就緒狀態,可以分為三種:
等待阻塞:執行狀態中的執行緒執行wait方法,使執行緒進入到阻塞狀態(wait會釋放持有的鎖)
同步阻塞:執行緒在獲取synchronize同步鎖是被其它執行緒佔用,因為同步所被其他執行緒佔用,則jvm會把該執行緒放入鎖池中
其它阻塞:通過呼叫執行緒的sleep或join發出了I/O請求時,執行緒就會進入到阻塞狀態,當sleep狀態超時,join等待執行緒終止或超時,或者I/O處理完畢,執行緒重新轉入就緒狀態(注意sleep不會釋放執行緒所持有的鎖)

死亡狀態:一個執行狀態的執行緒火氣其它終止條件發生時,該執行緒就切換到終止狀態

sleep和wait方法的區別?
這兩個方法來自不同的類分別是Thread和Object,sleep方法屬於Thread類中的靜態方法,wait方法是屬於Object的成員方法
最主要的是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其它執行緒可以使用同步控制塊或方法

wait和notify和notifyAll只能在同步控制方法或同步控制塊中使用,而sleep可以在任何地方使用。

sleep方法:表示讓一個執行緒進入休眠狀態,等待一定的時間後,自動醒來會進入到可執行狀態,不會馬上進入到執行狀態,因為執行緒排程機制恢復執行緒的執行也需要時間,一個執行緒物件呼叫了sleep之後,並不會釋放它所持有的物件鎖,所以也就不會影響其他執行緒物件的執行,但在sleep的過程中有可能被其他物件呼叫它的interrupt,產生InterruptedException,如果你的程式不捕獲這個異常,執行緒就會異常終止,進入TERMINATED狀態,如果你的程式捕獲了這個異常,那麼程式就會執行catch語句塊,可能還有fincally之後程式碼

wait方法:

wait屬於Object方法,一旦一個物件呼叫了這個防範必須要採用notify和notifyAll方法喚醒該執行緒,如果執行緒擁有某個或同步鎖,那麼在呼叫了wait方法後,這個執行緒就會釋放它所持有的所有同步資源,而不限於這個被呼叫了wait方法的物件,wait方法也同樣會在執行過程中被其他物件呼叫interrupt方法而產生InterruptException,效果以及處理方式同Sleep方法

執行緒中join方法的作用?
等待該執行緒終止
等待該執行緒執行完,才去繼續執行和同步順序執行差不多
舉個例子:
現在有A,B,C三個事情,只有做完A和B之後才能去做C,而A和B可以並行完成

這裡寫圖片描述

啟動一個執行緒Thread是用run方法還是start方法
啟動一個執行緒是呼叫satrt方法,使得執行緒處於就緒狀態,以後可以被jvm排程為執行狀態,一個執行緒必須關聯一些具體的執行程式碼,run方法就是該執行緒所關聯的執行程式碼

實現並啟動執行緒有兩種方法:
1.寫一個類繼承Thread類,重寫run方法,用satrt方法啟動執行緒
2.寫一個類實現Runnable介面,實現run方法,用 new Thread(Runnable target).start()方法來啟動

多執行緒原理,呼叫start方法之後,執行緒會被放到等待佇列,等待CPU排程,並不一定要馬上開始執行,只是這個執行緒置於就緒狀態,然後通過JVM,執行緒Thread會呼叫run方法,執行本執行緒的執行緒體

先呼叫start方法,後呼叫run,這麼麻煩,為什麼不直接呼叫run?就是為了實現多執行緒的優點,沒這個start不行

這裡寫圖片描述

這裡寫圖片描述

分別呼叫兩個執行緒的start方法,thread1和thread2交叉執行的
分別呼叫兩個執行緒的run方法:thread1和thread2順序執行

1.呼叫start方法來啟動執行緒,真正實現了多執行緒執行,這時無需等待run方法體程式碼執行完畢,可以直接繼續執行下面的程式碼(啟動執行緒等待cpu的排程),通過Thread類的start方法來啟動一個執行緒,這時執行緒是出於就緒狀態,並沒有執行,(獲取CPU資源排程後)然後通過此Thread類呼叫方法run來完成其執行操作。這裡run方法執行緒體,它包含了要執行的這個執行緒的那日哦那個,run方法執行結束後此執行緒終止,然後CPU再排程其它執行緒

2.run方法當作普通方法的方式呼叫,程式還是要順序執行,要等待run方法體執行完畢後,才可以繼續執行下面的程式碼,程式中只有主執行緒這一個執行緒,其程式執行路徑還是一條,這樣就沒有達到多執行緒的目的

簡述synchronized和Lock的區別?
1.Lock是一個介面,而synchronized是java中的關鍵字
2.synchronized在發生異常時,會自動釋放執行緒佔用的鎖,因此不會導致死鎖現象發生,而Lock在發生異常時,如果沒有主動去通過unlock方法去釋放鎖,很可能會導致死鎖現象,因此使用Lock時需要在finally塊中釋放鎖

3.Lock可以讓等待鎖的執行緒響應中斷,而synchronized不行,使用synchronized時,等待的執行緒會一直等待下去,不能夠響應中斷
4.通過Lock可以知道有沒有成功獲取鎖,兒synchronized卻無法辦到
5.Lock可以提高多個執行緒進行讀操作的效率
在效能上來講,如果競爭資源不激烈,兩者效能差不多的,首先選用synchronized,而當競爭資源非常激烈時(即有大量的執行緒同時競爭),此時Lock的效能遠遠高於synchronized,所以說,在具體使用時要根據具體情況選擇

synchronized實現的機制依賴於軟體層面上的JVM,synchronized會自動釋放鎖,而Lock一定要求程式設計師手工釋放,並且必須通過finally來釋放鎖
ReentantLock繼承介面Lock並且實現了介面中定義的方法,除了能完成synchronized所能完成的所有工作以外,還提供了諸如響應中斷鎖,可輪詢鎖請求,定時鎖等避免多執行緒死鎖的方法

ReentrantLock預設—競爭鎖機制

這裡寫圖片描述

這裡寫圖片描述

如何得不到鎖直接放棄的話,那麼1000個執行緒不可能全部得到鎖,

儘管java實現鎖機制有很多種,並且有些鎖機制效能也比synchronized高,但是還是強烈建議在多執行緒應用程式中使用該關鍵字synchronized,因為實現方便,後續工作由jvm完成,可靠性高,只有在確定鎖機制是當前多執行緒程式的效能瓶頸時,才會考慮其他鎖機制,如RenntrantLock等(ReadLock、WriteLock)

RenntrantLock通過方法lock與unlock來進行加鎖和解鎖的操作,與synchronized會被jvm控制自動解鎖機制不同,ReentrantLock加鎖後需要手動進行解鎖,為了避免程式出現異常而無法解鎖的情況,使用ReentrantLock必須在finally控制塊中進行解鎖操作

什麼是執行緒區域性變數和原理?

1.ThreadLocal概述:
ThreadLocal,顧名思義:它不是一個執行緒,而是執行緒的一個本地化物件,當工作於多執行緒中的物件使用ThreadLocal維護變數時,ThreadLocal為每一個使用該變數的執行緒分配一個獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其他執行緒所對應的副本,從執行緒的角度看,這個變數就是執行緒的本地變數

1,ThreadLocal不是執行緒,是執行緒的一個變數,你可以先簡單的理解為執行緒類的屬性變數
2.ThreadLocal是在類中通常定義的靜態變數
3.每個執行緒都有自己的ThreadLocal,它是變數的一個拷貝,修改它不影響其他執行緒

總結:每個執行緒獨立擁有一個變數,單個執行緒內共享,多個執行緒不共享
TheradLocal適用多執行緒資源共享,但不共享變化

ThreadLocal究竟是如何工作的?
1.Thread類中有一個成員變數叫ThreadLocalMap,它是一個Map,它的key是ThreadLocal類
2.每個執行緒擁有自己的申明為ThreadLocal型別的變數,所以這個類的名字叫ThreadLocal,執行緒自己的變數
3.此變數的生命週期是由執行緒決定的,開始於第一次初始化(get或者set方法)
4.由ThreadLocal的工作原理決定了,每個執行緒獨自擁有一個變數,並非共享或者拷貝

java中常見的執行緒池?

建立執行緒需要花費昂貴的資源和時間,如果任務來了才建立執行緒那麼響應時間會變長,而且一個程序能建立的執行緒數有限, 為了避免這些問題,在程式啟動的時候就建立若干執行緒來響應處理,它們被稱作為執行緒池,裡面的執行緒叫工作執行緒,從JDK1.5開始,javaAPI提供了Executor框架可以讓你建立不同的執行緒池

常見的執行緒池:
newSingleThreadExecutor:建立一個單執行緒的執行緒池,這個執行緒池只有一個執行緒在工作,也就是相當於單執行緒序列執行所有任務,如果這個唯一的執行緒因為異常而結束,那麼會有一個新的執行緒來替代它,此執行緒池保證所有的任務的執行順序按照任務的提交順序執行

newFixedThreadPool:建立固定大小的執行緒池,每次提交一個任務就建立一個執行緒,知直執行緒達到執行緒池的最大大小,執行緒池大小一旦達到最大值就會保持不變,如果某個執行緒因為執行異常而結束,那麼執行緒池會補充一個新執行緒

newCachedThreadPool:建立一個可快取的執行緒池,如果執行緒池的大小超過了處理任務所需要的執行緒,那麼就會回收部分空閒的執行緒,當任務增加時,此執行緒池又可以智慧的新增新的執行緒來處理任務,此執行緒池不會對執行緒池的大小做限制,執行緒池的大小完全依賴於作業系統或者說JVM能夠建立的最大執行緒的大小

newScheduledThreadLocal:
建立一個大小無限的執行緒池,此執行緒池支援定時以及週期性執行任務的需求

java對執行緒池的支援的比較重要的幾個概念:
Exector:執行緒池的頂級介面(相當於集合類的Collection)
ExecutorServcie:真正的執行緒池介面,繼承Exector
ScheduledExecutorServcie:能和timer/timerTask類似,解決那些需要任務重複執行的問題
ThreadPoolExector :ExecutorServcie的預設實現
ScheduledThreadPoolExector:繼承ThreadPoolExector的ScheduledExecutorServcie介面的實現,週期性任務排程的類實現