多線程
1.樂觀鎖、CAS思想
java樂觀鎖機制:
? 樂觀鎖體現的是悲觀鎖的反面。它是一種積極的思想,它總是認為數據是不會被修改的,所以是不會對數據上鎖的。但是樂觀鎖在更新的時候會去判斷數據是否被更新過。樂觀鎖的實現方案一般有兩種(版本號機制和CAS)。樂觀鎖適用于讀多寫少的場景,這樣可以提高系統的并發量。在Java中 java.util.concurrent.atomic下的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的。
? 樂觀鎖,大多是基于數據版本 (Version)記錄機制實現。即為數據增加一個版本標識,在基于數據庫表的版本解決方案中,一般是通過為數據庫表增加一個 “version” 字段來 實現。讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提 交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據 版本號大于數據庫表當前版本號,則予以更新,否則認為是過期數據。
CAS思想:
? CAS就是compare and swap(比較交換),是一種很出名的無鎖的算法,就是可以不使用鎖機制實現線程間的同步。使用CAS線程是不會被阻塞的,所以又稱為非阻塞同步。CAS算法涉及到三個操作:
需要讀寫內存值V;進行比較的值A;準備寫入的值B
當且僅當V的值等于A的值等于V的值的時候,才用B的值去更新V的值,否則不會執行任何操作(比較和替換是一個原子操作-A和V比較,V和B替換),一般情況下是一個自旋操作,即不斷重試。
? ABA問題和高并發的情況下,很容易發生并發沖突,如果CAS一直失敗,那么就會一直重試,浪費CPU資源
2.synchronized
使用方法:主要的三種使??式
修飾實例?法: 作?于當前對象實例加鎖,進?同步代碼前要獲得當前對象實例的鎖
修飾靜態?法: 也就是給當前類加鎖,會作?于類的所有對象實例,因為靜態成員不屬于任何?個實例對象,是類成員。
修飾代碼塊: 指定加鎖對象,對給定對象加鎖,進?同步代碼庫前要獲得給定對象的鎖。
總結:synchronized鎖住的資源只有兩類:一個是對象,一個是類。
JDK6之后對synchronized進行了優化,新增了兩種狀態,總共就是四個狀態:無鎖狀態、偏向鎖、輕量級鎖、重量級鎖.
3.ReenTrantLock
和synchronized區別:
- **底層實現 **:synchronized 是JVM層面的鎖,是Java關鍵字,ReentrantLock 是從jdk1.5以來(java.util.concurrent.locks.Lock)提供的API層面的鎖。
- 實現原理:synchronized 的實現涉及到鎖的升級,具體為無鎖、偏向鎖、自旋鎖、向OS申請重量級鎖;ReentrantLock實現則是通過利用CAS(CompareAndSwap)自旋機制保證線程操作的原子性和volatile保證數據可見性以實現鎖的功能。
- 是否可手動釋放:synchronized 不需要用戶去手動釋放鎖,synchronized 代碼執行完后系統會自動讓線程釋放對鎖的占用;ReentrantLock則需要用戶去手動釋放鎖,如果沒有手動釋放鎖,就可能導致死鎖現象。
- **是否可中斷:**synchronized是不可中斷類型的鎖,除非加鎖的代碼中出現異常或正常執行完成;ReentrantLock則可以中斷,可通過trylock(long timeout,TimeUnit unit)設置超時方法或者將lockInterruptibly()放到代碼塊中,調用interrupt方法進行中斷。
- **是否公平鎖:**synchronized為非公平鎖 ReentrantLock則即可以選公平鎖也可以選非公平鎖,通過構造方法new ReentrantLock時傳入boolean值進行選擇,為空默認false非公平鎖,true為公平鎖,公平鎖性能非常低。
4.公平鎖和非公平鎖區別
公平鎖:
公平鎖自然是遵循FIFO(先進先出)原則的,先到的線程會優先獲取資源,后到的會進行排隊等待。
優點:所有的線程都能得到資源,不會餓死在隊列中。適合大任務。
公平鎖效率低原因:
公平鎖要維護一個隊列,后來的線程要加鎖,即使鎖空閑,也要先檢查有沒有其他線程在 wait,如果有自己要掛起,加到隊列后面,然后喚醒隊列最前面線程。這種情況下相比較非公平鎖多了一次掛起和喚醒。
線程切換的開銷,其實就是非公平鎖效率高于公平鎖的原因,因為非公平鎖減少了線程掛起的幾率,后來的線程有一定幾率逃離被掛起的開銷。
非公平鎖:
多個線程去獲取鎖的時候,會直接去嘗試獲取,獲取不到,再去進入等待隊列,如果能獲取到,就直接獲取到鎖。
優點:可以減少CPU喚醒線程的開銷,整體的吞吐效率會高點,CPU也不必取喚醒所有線程,會減少喚起線程的數量。
缺點:你們可能也發現了,這樣可能導致隊列中間的線程一直獲取不到鎖或者長時間獲取不到鎖。
5.ThreadLocal
ThreadLocal簡介:
通常情況下,我們創建的變量是可以被任何?個線程訪問并修改的。如果想實現每?個線程都有??的。
專屬本地變量該如何解決呢?JDK中提供的 ThreadLocal 類正是為了解決這樣的問題。類似操作系統中的TLAB。
原理:
首先 ThreadLocal 是一個泛型類,保證可以接受任何類型的對象。因為一個線程內可以存在多個 ThreadLocal 對象,所以其實是 ThreadLocal 內部維護了一個 Map ,是 ThreadLocal 實現的一個叫做 ThreadLocalMap 的靜態內部類。
最終的變量是放在了當前線程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解為只是ThreadLocalMap的封裝,傳遞了變量值。我們使用的 get()、set() 方法其實都是調用了這個ThreadLocalMap類對應的 get()、set() 方法。
ThreadLocal內存泄漏的場景
? 實際上 ThreadLocalMap 中使用的 key 為 ThreadLocal 的弱引用,? value 是強引?。弱引用的特點是,如果這個對象持有弱引用,那么在下一次垃圾回收的時候必然會被清理掉。
? 所以如果 ThreadLocal 沒有被外部強引用的情況下,在垃圾回收的時候會被清理掉的,這樣一來 ThreadLocalMap中使用這個 ThreadLocal 的 key 也會被清理掉。但是,value 是強引用,不會被清理,這樣一來就會出現 key 為 null 的 value。假如我們不做任何措施的話,value 永遠?法被GC 回收,如果線程長時間不被銷毀,可能會產?內存泄露。
? ThreadLocalMap實現中已經考慮了這種情況,在調用 set()、get()、remove() 方法的時候,會清理掉 key 為 null 的記錄。如果說會出現內存泄漏,那只有在出現了 key 為 null 的記錄后,沒有手動調用 remove() 方法,并且之后也不再調用 get()、set()、remove() 方法的情況下。因此使?完ThreadLocal ?法后,最好?動調? remove() ?法。
6.HashMap線程安全
死循環造成 CPU 100%
HashMap 有可能會發生死循環并且造成 CPU 100% ,這種情況發生最主要的原因就是在擴容的時候,也就是內部新建新的 HashMap 的時候,擴容的邏輯會反轉散列桶中的節點順序,當有多個線程同時進行擴容的時候,由于 HashMap 并非線程安全的,所以如果兩個線程同時反轉的話,便可能形成一個循環,并且這種循環是鏈表的循環,相當于 A 節點指向 B 節點,B 節點又指回到 A 節點,這樣一來,在下一次想要獲取該 key 所對應的 value 的時候,便會在遍歷鏈表的時候發生永遠無法遍歷結束的情況,也就發生 CPU 100% 的情況。
所以綜上所述,HashMap 是線程不安全的,在多線程使用場景中推薦使用線程安全同時性能比較好的 ConcurrentHashMap。
7.String不可變原因
- 可以使用字符串常量池,多次創建同樣的字符串會指向同一個內存地址;
- 可以很方便地用作 HashMap 的 key。通常建議把不可變對象作為 HashMap的 key;
- hashCode生成后就不會改變,使用時無需重新計算;
- 線程安全,因為具備不變性的對象一定是線程安全的;
8.volatile
作用:
保證數據的“可見性”:被volatile修飾的變量能夠保證每個線程能夠獲取該變量的最新值,從而避免出現數據臟讀的現象。
禁止指令重排:在多線程操作情況下,指令重排會導致計算結果不一致。
9.死鎖條件、解決方式
死鎖是指兩個或兩個以上進程在執行過程中,因爭奪資源而造成的下相互等待的現象;
死鎖的條件:
- 互斥條件:進程對所分配到的資源不允許其他進程訪問,若其他進程訪問該資源,只能等待至占有該資源的進程釋放該資源。
- 請求與保持條件:進程獲得一定的資源后,又對其他資源發出請求,阻塞過程中不會釋放自己已經占有的資源。
- 非剝奪條件:進程已獲得的資源,在未完成使用之前,不可被剝奪,只能在使用后自己釋放。
- 循環等待條件:系統中若干進程組成環路,環路中每個進程都在等待相鄰進程占用的資源。
**解決方法:**破壞死鎖的任意一條件。
- 樂觀鎖,破壞資源互斥條件,CAS。
- 資源一次性分配,從而剝奪請求和保持條件、tryLock。
- 可剝奪資源:即當進程新的資源未得到滿足時,釋放已占有的資源,從而破壞不可剝奪的條件,數據庫deadlock超時。
- 資源有序分配法:系統給每類資源賦予一個序號,每個進程按編號遞增的請求資源,從而破壞環路等待的條件,轉賬場景。