從網上收集整理了面試題,雖然整理了但是好多都記不住,需要每天鞏固,
一、什么是執行緒安全
當多個執行緒訪問某一個類(物件或方法)時,物件對應的公共資料區始終都能表現正確,那么這個類(物件或方法)就是執行緒安全的,
執行緒安全的代碼是多個執行緒同時執行也能正常作業的代碼
如果一段代碼可以保證多個執行緒訪問的時候正確操作共享資料,那么它是執行緒安全的,
二、創建執行緒的方式
繼承Thread類創建執行緒
實作Runnable介面創建執行緒
使用Callable和Future創建執行緒
使用執行緒池創建(使用java.util.concurrent.Executor介面)
1、繼承Thread類創建執行緒類
(1)定義Thread類的子類,并重寫該類的run方法,該run方法的方法體就代表了執行緒要完成的任務,因此把run()方法稱為執行體,
(2)創建Thread子類的實體,即創建了執行緒物件,
(3)呼叫執行緒物件的start()方法來啟動該執行緒,
2、通過實作Runnable介面創建執行緒類
(1)定義runnable介面的實作類,并重寫該介面的run()方法,該run()方法的方法體同樣是該執行緒的執行緒執行體,
(2)創建 Runnable實作類的實體,并依此實體作為Thread的target來創建Thread物件,該Thread物件才是真正的執行緒物件,
(3)呼叫執行緒物件的start()方法來啟動該執行緒,
3、通過Callable和Future創建執行緒
(1)創建Callable介面的實作類,并實作call()方法,該call()方法將作為執行緒執行體,并且有回傳值,
(2)創建Callable實作類的實體,使用FutureTask類來包裝Callable物件,該FutureTask物件封裝了該Callable物件的call()方法的回傳值,
(3)使用FutureTask物件作為Thread物件的target創建并啟動新執行緒,
(4)呼叫FutureTask物件的get()方法來獲得子執行緒執行結束后的回傳值,
4、使用執行緒池創建(使用java.util.concurrent.Executor介面)
三、Runnable和Callable的區別
- Runnable沒有回傳值;Callable可以回傳執行結果,是個泛型,和Future、FutureTask配合可以用來獲取異步執行的結果
- Callable介面的call()方法允許拋出例外;Runnable的run()方法例外只能在內部消化,不能往上繼續拋
注:Callalble介面支持回傳執行結果,需要呼叫FutureTask.get()得到,此方法會阻塞主行程的繼續往下執行,如果不呼叫不會阻塞,
四、wait方法和sleep方法的區別
- sleep是Thread類的方法,wait是Object類的方法,
- 最主要是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他執行緒可以使用同步控制塊或者方法(鎖代碼塊和方法鎖),
- wait,notify和notifyAll只能在同步控制方法或者同步控制塊里面使用,而sleep可以在任何地方使用(使用范圍), sleep必須捕獲例外,而wait,notify和notifyAll不需要捕獲例外 ,
- sleep方法是Thread類的靜態方法,呼叫此方法會讓當前執行緒暫停指定的時間,將執行機會(CPU)讓給其他執行緒,但是不會釋放鎖,因此休眠時間結束后自動恢復(程式回到就緒狀態),
- wait是Object類的方法,呼叫物件的wait方法導致執行緒放棄CPU的執行權,同時也放棄物件的鎖(執行緒暫停執行),進入物件的等待池(wait pool),只有呼叫物件的notify或notifyAll方法才能喚醒等待池中的執行緒進入等鎖池(lock pool),如果執行緒重新獲得物件的鎖就可以進入就緒狀態,
- wait 可以指定時間也可以不指定,指定時間 wait(time) 在 time時間內 有別的執行緒 notifyAll() 是不會喚醒到它 ,sleep 必須指定時間,
五、synchronized和ReentrantLock區別
1)Lock是一個介面,synchronized是Java中的關鍵字,synchronized是內置的語言實作;
2)synchronized發生例外時,會自動釋放執行緒占用的鎖,故不會發生死鎖現象,Lock發生例外,若沒有主動釋放,極有可能造成死鎖,故需要在finally中呼叫unLock方法釋放鎖;
3)Lock可以讓等待鎖的執行緒回應中斷,使用synchronized只會讓等待的執行緒一直等待下去,不能回應中斷
4)通過Lock可以知道有沒有成功獲取到鎖,synchronized就不靈
5)Lock可以提高多個執行緒進行讀操作的效率,
ReentrantLock是Lock的實作類,是一個互斥的同步器,在多執行緒高競爭條件下,ReentrantLock比synchronized有更加優異的性能表現
底層實作上來說,synchronized 是JVM層面的鎖,是Java關鍵字,通過monitor物件來完成(monitorenter與monitorexit),物件只有在同步塊或同步方法中才能呼叫wait/notify方法,ReentrantLock 是從jdk1.5以來(java.util.concurrent.locks.Lock)提供的API層面的鎖,
是否可手動釋放:
synchronized 不需要用戶去手動釋放鎖,synchronized 代碼執行完后系統會自動讓執行緒釋放對鎖的占用; ReentrantLock則需要用戶去手動釋放鎖,如果沒有手動釋放鎖,就可能導致死鎖現象,一般通過lock()和unlock()方法配合try/finally陳述句塊來完成,使用釋放更加靈活,
是否可中斷
synchronized是不可中斷型別的鎖,除非加鎖的代碼中出現例外或正常執行完成; ReentrantLock則可以中斷,可通過trylock(long timeout,TimeUnit unit)設定超時方法或者將lockInterruptibly()放到代碼塊中,呼叫interrupt方法進行中斷,
是否公平鎖
synchronized為非公平鎖 ReentrantLock則即可以選公平鎖也可以選非公平鎖,通過構造方法new ReentrantLock時傳入boolean值進行選擇,為空默認false非公平鎖,true為公平鎖,
鎖是否可系結條件Condition
synchronized不能系結; ReentrantLock通過系結Condition結合await()/singal()方法實作執行緒的精確喚醒,而不是像synchronized通過Object類的wait()/notify()/notifyAll()方法要么隨機喚醒一個執行緒要么喚醒全部執行緒,
鎖的物件
synchronzied鎖的是物件,鎖是保存在物件頭里面的,根據物件頭資料來標識是否有執行緒獲得鎖/爭搶鎖;ReentrantLock鎖的是執行緒,根據進入的執行緒和int型別的state標識鎖的獲得/爭搶,
兩者的共同點:
1. 都是用來協調多執行緒對共享物件、變數的訪問
2. 都是可重入鎖,同一執行緒可以多次獲得同一個鎖
3. 都保證了可見性和互斥性
兩者的不同點:
1. ReentrantLock 顯示的獲得、釋放鎖,synchronized 隱式獲得釋放鎖
2. ReentrantLock 可回應中斷、可輪回,synchronized 是不可以回應中斷的,為處理鎖的
不可用性提供了更高的靈活性
3. ReentrantLock 是 API 級別的,synchronized 是 JVM 級別的
4. ReentrantLock 可以實作公平鎖
5. ReentrantLock 通過 Condition 可以系結多個條件
6. 底層實作不一樣, synchronized 是同步阻塞,使用的是悲觀并發策略,lock 是同步非阻
塞,采用的是樂觀并發策略
7. Lock 是一個介面,而 synchronized 是 Java 中的關鍵字,synchronized 是內置的語言
實作,
8. synchronized 在發生例外時,會自動釋放執行緒占有的鎖,因此不會導致死鎖現象發生;
而 Lock 在發生例外時,如果沒有主動通過 unLock()去釋放鎖,則很可能造成死鎖現象,
因此使用 Lock 時需要在 finally 塊中釋放鎖,
9. Lock 可以讓等待鎖的執行緒回應中斷,而 synchronized 卻不行,使用 synchronized 時,
等待的執行緒會一直等待下去,不能夠回應中斷,
10. 通過 Lock 可以知道有沒有成功獲取鎖,而 synchronized 卻無法辦到,
11. Lock 可以提高多個執行緒進行讀操作的效率,既就是實作讀寫鎖等
六、CAS無鎖技術(簡單了解即可,參考https://blog.csdn.net/ls5718/article/details/52563959)
七、volatile關鍵字的作用和原理(參考https://blog.csdn.net/zezezuiaiya/article/details/81456060)
被volatile關鍵字修飾的變數,編譯器與運行時都會注意到這個變數是共享的,因此不會將該變數上的操作與其他記憶體操作一起重排序,volatile變數不會被快取在暫存器或者對其他處理器不可見的地方,因此在讀取volatile型別的變數時總會回傳最新寫入的值,
在訪問volatile變數時不會執行加鎖操作,因此也就不會使執行執行緒阻塞,因此volatile變數是一種比sychronized關鍵字更輕量級的同步機制,當對非 volatile 變數進行讀寫的時候,每個執行緒先從記憶體拷貝變數到CPU快取中,如果計算機有多個CPU,每個執行緒可能在不同的CPU上被處理,這意味著每個執行緒可以拷貝到不同的 CPU cache 中,而宣告變數是 volatile 的,JVM 保證了每次讀變數都從記憶體中讀,跳過 CPU cache 這一步,
可見性是指當多個執行緒訪問同一個變數時,一個執行緒修改了這個變數的值,其他執行緒能夠立即看得到修改的值,Java中的volatile關鍵字提供了一個功能,那就是被其修飾的變數在被修改后可以立即同步到主記憶體,被其修飾的變數在每次是用之前都從主記憶體重繪,因此,可以使用volatile來保證多執行緒操作時變數的可見性,
volatile不具備原子性,但是擁有可見性,并且在一定程度上擁有有序性,
八、ThreadLocal和ThreadPoolExectutor
ThreadLocal叫做執行緒變數,意思是ThreadLocal中填充的變數屬于當前執行緒,該變數對其他執行緒而言是隔離的,ThreadLocal為變數在每個執行緒中都創建了一個副本,那么每個執行緒可以訪問自己內部的副本變數,
使用場景
- 在進行物件跨層傳遞的時候,使用ThreadLocal可以避免多次傳遞,打破層次間的約束,
- 執行緒間資料隔離
- 進行事務操作,用于存盤執行緒事務資訊,
- 資料庫連接,Session會話管理,
ThreadPoolExectutor(需要完善)
九、常見執行緒池
Java通過Executors提供四種執行緒池,分別為:
newCachedThreadPool創建一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閑執行緒,若無可回收,則新建執行緒,
newFixedThreadPool 創建一個定長執行緒池,可控制執行緒最大并發數,超出的執行緒會在佇列中等待,
newScheduledThreadPool 創建一個定長執行緒池,支持定時及周期性任務執行,
newSingleThreadExecutor 創建一個單執行緒化的執行緒池,它只會用唯一的作業執行緒來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行,
十、分布式環境下如何保證執行緒安全
避免并發
在分布式環境中,如果存在并發問題,那么很難通過技術去解決,或者解決的代價很大,所以我們首先要想想是不是可以通過某些策略和業務設計來避免并發,比如通過合理的時間調度,避開共享資源的存取沖突,另外,在并行任務設計上可以通過適當的策略,保證任務與任務之間不存在共享資源,比如在以前博文中提到的例子,我們需要用多執行緒或分布式集群來計算一堆客戶的相關統計值,由于客戶的統計值是共享資料,因此會有并發潛在可能,但從業務上我們可以分析出客戶與客戶之間 資料是不共享的,因此可以設計一個規則來保證一個客戶的計算作業和資料訪問只會被一個執行緒或一臺作業機完成,而不是把一個客戶的計算作業分配給多個執行緒去 完成,這種規則很容易設計,例如可以采用hash演算法,
時間戳
分布式環境中并發是沒法保證時序的,無論是通過遠程介面的同步呼叫或異步訊息,因此很容易造成某些對時序性有要求的業務在高并發時產生錯誤,比如系統A需要把某個值的變更同步到系統B,由于通知的時序問題會導致一個過期的值覆寫了有效值,對于這個問題,常用的辦法就是采用時間戳的方式,每次系統A發送變更給系統B的時候需要帶上一個能標示時序的時間戳,系統B接到通知后會拿時間戳與存在的時間戳比較,只有當通知的時間戳大于存在的時間戳,才做更新,這種方式比較簡單,但關鍵在于呼叫方一般要保證時間戳的時序有效性,
串行化
有的時候可以通過串行化可能產生并發問題操作,犧牲性能和擴展性,來滿足對資料一致性的要求,比如分布式訊息系統就沒法保證訊息的有序性,但可以通過變分布式訊息系統為單一系統就可以保證訊息的有序性了,另外,當接收方沒法處理呼叫有序性,可以通過一個佇列先把呼叫資訊快取起來,然后再串行地處理這些呼叫,
資料庫
分布式環境中的共享資源不能通過Java里同步方法或加鎖來保證執行緒安全,但資料庫是分布式各服務器的共享點,可以通過資料庫的高可靠一致性機制來滿足需求,比如,可以通過唯一性索引來解決并發程序中重復資料的生產或重復任務的執行;另外有些更新計算操作也盡量通過sql來完成,因為在程式段計算好后再去更新就有可能發生臟復寫問題,但通過一條sql來完成計算和更新就可以通過資料庫的鎖機制來保證update操作的一致性,
行鎖
有的事務比較復雜,無法通過一條sql解決問題,并且有存在并發問題,這時就需要通過行鎖來解決,一般行鎖可以通過以下方式來實作:
對于Oracle資料庫,可以采用select ... for update方式,這種方式會有潛在的危險,就是如果沒有commit就會造成這行資料被鎖住,其他有涉及到這行資料的任務都會被掛起,應該謹慎使用
在表里添加一個標示鎖的欄位,每次操作前,先通過update這個鎖欄位來完成類似競爭鎖的操作,操作完成后在update鎖欄位復位,標示已歸還鎖,這種方式比較安全,不好的地方在于這些update鎖欄位的操作就是額外的性能消耗
統一觸發途徑
當一個資料可能會被多個觸發點或多個業務涉及到,就有并發問題產生的隱患,因此可以通過前期架構和業務設計,盡量統一觸發途徑,觸發途徑少了一是減少并發的可能,也有利于對于并發問題的分析和判斷,
如何保證高并發時執行緒安全?
對于商城一類系統中,單點登錄、購物車、訂單這些都有并發,
用AtomicInteger、synchronized、Lock、ThreadLocal等類來保證在代碼層面上的執行緒安全;如果是功能上需要自主多執行緒處理,那么也會使用線程池ThreadPool來提高并發效率,
對高并發的處理會使用Redis的分布式鎖(setnx),將對于服務器的承載力達到一定數量后,之后的請求全部加入佇列處理,
負載均衡:在代碼層級上對不同的業務進行讀寫分離;而資料庫上進行集群和主從復制,在應用服務器上對應的對每個服務器都運用lvs+keepalive模式進行服務器集群;如果硬體資源足夠的話那么可以對集群節點更加多和更加分散提高并發能力和系統穩定性,
Redis是一個開源,先進的key-value存盤,并用于構建高性能,可擴展的Web應用程式的完美解決方案,是執行緒安全的,
Redis三個主要特點:
Redis資料庫完全在記憶體中,使用磁盤僅用于持久性,
相比許多鍵值資料存盤,Redis擁有一套較為豐富的資料型別(list,string,sort,set,hash),
Redis可以將資料復制到任意數量的從服務器,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/260394.html
標籤:其他
上一篇:Redis集群原理詳解
