JavaSE多執行緒
鎖的基礎知識:
鎖的型別:
悲觀鎖和樂觀鎖:
樂觀鎖:認為讀多寫少,遇到併發寫的可能性極低,即每次去拿資料的時候認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷在此期間有沒有 人更新這個資料。
判斷依據:在寫時先讀出當前版本號,然後加鎖操作(比較跟上一次的版本號,如果一樣就更新),如果失敗就重複 讀 -> 比較 ->寫的操作
Java中的樂觀鎖基本都是通過CAS操作實現的,CAS是一種更新的原子操作,比較當前值跟傳入值是否一樣,一樣則更新,否則失敗
CAS:
簡單的來說,CAS有三個運算元,記憶體值 V, 舊的預期值 A,要修改的新值B。當且僅當預期值A與記憶體值V相等時,將記憶體值修改為B,否則返回V。
這是一種樂觀鎖的思路。它相信在它之前沒有執行緒去修改記憶體值
缺點:會發生 ABA問題,即A被修改成B,然後又被修改成A,不能感知到修改
悲觀鎖:認為寫多讀少,遇到併發寫的可能性高,每次在讀寫資料的時候都會上鎖,如果別的執行緒想讀寫這個資料就會block直到拿到鎖
1.Synchronized底層實現(*****):
Java物件頭:鎖的物件儲存在物件頭中,synchronized鎖的是物件
鎖的狀態分為:自旋鎖,偏向鎖,輕量級鎖,重量級鎖)
自旋鎖:加鎖後,只有一個執行緒進入程式碼塊,其他執行緒等待(自旋等待),為重量級鎖(monitorenter,重量級鎖標誌)
自旋鎖:1.相當於怠速停車,具有不公平性,處於自旋狀態的鎖比重量級鎖(它處於阻塞狀態)更容易獲得鎖
2.自旋鎖不會引起呼叫者立即睡眠,如果自旋鎖已經被別的執行單元保持,呼叫者不放棄處理器的執行時間,進行忙迴圈(自旋),
跑的是無用的執行緒,JDK1.6後預設開啟了自旋鎖,自旋次數預設10次
3.自旋鎖一直佔用CPU,在未獲得鎖的情況下,一直進行執行 - - - 自旋,若不能在很短的時間內獲得鎖,將會使CPU效率降低
自適應自旋:1.減少無用執行緒佔用CPU的問題
2.自旋的時間不再是固定的,由前一個在同一個鎖上的自旋時間及鎖擁有者的狀態來決定。
3.如果在同一個鎖物件上,自選等待剛好成功獲得鎖,並且持有鎖的執行緒正在執行中,那麼虛擬機器就認為這次自旋很有可能會獲得鎖,
將會允許自旋等待更長的時間
重量級鎖(Java頭中的monitorenter物件):os需要從使用者態 -> 核心態,開銷較大。同一時刻多個執行緒競爭資源
輕量級鎖(CAS操作):多執行緒在不同時刻訪問共享資源,樂觀鎖的一種
偏向鎖:更為樂觀的鎖,假定從始至終都是同一個執行緒在訪問共享資源
JDK中鎖只有升級過程,沒有降級過程。 無鎖 -> 偏向鎖 -> 輕量級鎖 -> 重量級鎖
鎖消除:消除類似於Vector等執行緒安全集合不存在共享資源競爭時,JVM將程式碼優化,解鎖
2.synchronized 與 ReentrantLock 區別
synchronized:
synchronized是Java中最基本同步互斥的手段,可以修飾程式碼塊,方法,類
在修飾程式碼塊的時候需要一個reference物件作為鎖的物件
在修飾方法的時候預設當前物件作為鎖的物件
修飾類的時候預設當前類的class物件作為鎖的物件
synchronized會在進入同步塊的前後分別形成monitorenter和monitorexit位元組碼指令,在執行monitorenter指令時會嘗試獲取物件的鎖,如果
此物件沒有被鎖,或此物件已被當前執行緒鎖住,則鎖的計數器+1,如果monitorexit被鎖的物件的計數器-1,直到為0就釋放該物件的鎖,由此
synchronized是可重入的,不會將自己鎖死。
ReentrantLock:
除了synchronized的功能,多了三個高階功能
1.等待可中斷 2.公平鎖 3.繫結多個Condition
1.等待可中斷
在持有鎖的執行緒長時間不釋放鎖的時候,等待的執行緒可以放棄等待. tryLock(long timeout,TimeUnit unit)
2.公平鎖
按照申請鎖的順序來一次獲得鎖稱為公平鎖,synchronized的是非公平鎖,ReentrantLock可以通過建構函式實現公平鎖
new ReentrantLock(boolean fair)
3.繫結多個Condition
通過多次newCondition可以獲得多個Condition物件,可以簡單的實現比較複雜的執行緒同步的功能
總的來說,lock更加靈活
二者相同點: Lock能完成synchronized所實現的所有功能
3.Lock(AQS: AbstractQueuedSynchronized 核心概念:一個是表示(鎖)狀態的變數、一個是佇列)
4.Lock與Synchronized的區別
a.synchronized內建關鍵字,在JVM層面實現,發生異常時,會自動釋放執行緒佔有的鎖,因此不會發生死鎖現象。
Lock在發生異常時,如果沒有主動通過unLock去釋放鎖,很可能產生死鎖現象,因此使用Lock時需要在finally塊中釋放鎖
b. Lock具有高階特性: 時間鎖等候,可中斷鎖等候
c.當競爭資源非常激烈時(即有大量執行緒同時競爭時),此時Lock的效能要遠遠優於synchronized
5.執行緒池
JDK內建的四大執行緒池:
1.建立無大小限制的執行緒池:
public static ExecutorService newCachedThreadPool()
2.建立固定大小的執行緒池:
public static ExecutorService newFixedThreadPool(int nThreads)
3.單執行緒池
public static ExecutorService newSingleThreadExecutor()
4.建立排程執行緒池
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
執行緒池的三大優點:
a.降低資源消耗:通過重複利用已建立的執行緒降低執行緒建立與銷燬帶來的消耗
b.提高響應速度:當任務到達時,不需要等待執行緒建立就可以立即執行
c.提高執行緒建立的可管理性:使用執行緒池可以統一進行執行緒分配、排程和監控
執行緒池的組成:
1. corePool:核心執行緒池
2. BlockingQueue: 阻塞佇列
3. MaxPool: 執行緒池容納的最大執行緒容量
a. 如果當前執行的執行緒數 < corePoolSize ,則建立新的執行緒執行任務,然後將其放入corePool(需要全域性鎖)
b. 如果當前執行緒數 >= corePoolSize,將任務放入阻塞佇列等待排程執行 (95%)
c. 如果阻塞佇列已滿,則試圖建立新的執行緒來執行任務(需要全域性鎖)
d. 如果建立執行緒後,匯流排程數 > maxPoolSize,則任務被拒絕,呼叫拒絕策略返回給使用者
工作執行緒:
執行緒池建立執行緒時,將執行緒封裝為worker,worker在執行完任務後,還會迴圈從工作佇列中取得任務來執行
手工建立執行緒池的六個引數:
ThreadPoolExecutor tye = new ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler
)
1. corePoolSize(核心執行緒池): 當提交一個任務到執行緒池時,執行緒池會建立一個新的執行緒執行這個任務,即使其他基本執行緒也可以執行這個任務也會建立 新的執行緒,直到當前執行緒池中的執行緒數量大於基本大小
2. BlockingQueue(阻塞佇列): 用於儲存等待執行任務的阻塞佇列
a. ArrayBlockingQueue: 基於陣列的有界阻塞佇列。按照FIFO對元素排序
b. LinkedBlockingQueue: 基於連結串列的無界阻塞佇列,吞吐量高於 ArrayBlockingQueue
FixedThreadPool(),SingleThreadPool() 都用此佇列
c. SynchronousQueue: 不儲存元素的阻塞佇列,每個插入操作必須等待另一個執行緒移除操作,否則插入操作一直處於阻塞狀態
吞吐量 > LinkedBlockingQueue
cachedThreadPool()採用此佇列
d. PriorityBlockingQueue: 具有優先順序的無界阻塞佇列
3.keepAliveTime(執行緒活動保持時間): 執行緒的工作執行緒空閒後,保持存活的時間
4.TimeUnit : KeepAliveTime 的時間單位
5.RejectedExecutionHandler(飽和策略)(拒絕策略):執行緒池滿時無法處理新任務的執行策略
執行緒池預設採用 AbortPolicy (丟擲異常)-----可以省略此引數
6.死鎖的產生原因(四大條件)以及處理方案(銀行家演算法)
a. 互斥條件:程序對所分配的資源進行排他性使用,即一段時間內,某資源只能由一個程序佔用,如果此時還有其他程序請求該資源,則請求者只能等 待
b. 請求和保持條件:即程序已經保持至少保持一個資源,但是還在請求別的資源,但此時別的資源被其他執行緒持有,此時請求程序阻塞,此程序也不 想放棄所持有的資源
c. 不剝奪條件:程序已經獲得的資源,不能被剝奪,只能由程序使用完自己釋放
d. 環路等待條件:在發生死鎖時,必然存在一個競爭資源的環形鏈。即程序集合中 {p0,p1,p2,...,pn},p0等待p1的資源,pn等待p0的資源
銀行家演算法:
是用來避免作業系統出現死鎖的有效演算法
7. volatile 兩層語義 - 懶漢式單例為何使用雙重加鎖
a.禁止指令重排
b.保證記憶體可見性
// 懶漢式單例模式
// 宣告 : 程式碼中出現的問題前提是 第二行程式碼未被 volatile修飾
public class Singleton{ // 1
private static volatile Singleton singleton; // 2
private Singleton(){}
public static Singleton getInstance(){ // 3
// 雙重檢查
if(singleton==null){ // 4 第一次檢查
synchronized(Singleton.class){ // 5 加鎖
if(singleton==null){ // 6 第二次檢查
return singleton = new Singleton(); // 7 問題在這裡
}
}
}
return singleton;
}
}
在多執行緒情況下,上面第七行程式碼 singleton = new Singleton(); 建立一個物件分為三步
memory = allocate(); // 1:分配物件的記憶體空間
ctorInstance(memory); // 2:初始化物件
singleton = memory; // 3.設定 singleton 指向 剛分配的記憶體地址
上面三行程式碼可能會被重排序:
memory = allocate(); // 1:分配物件的記憶體空間
singleton = memory; // 3.設定 singleton 指向 剛分配的記憶體地址
ctorInstance(memory); // 2:初始化物件
這裡由一個問題就是,在還沒有初始化物件的時候就將singleton指向了記憶體地址
8.NIO(Netty)
NIO如何實現多路複用,BIO,NIO,AIO特點
多路複用:在Java 1.4中引入了NIO框架(Java.nio包),提供了Channel,Selector,Buffer等新的抽象類,可以構建多路複用的,同步非阻塞IO程式,同時提供了更接近作業系統底層的高效能資料操作方式
AIO:在JDK1.7中,NIO有了新一步改進,既 NIO2 ,引入了非同步非阻塞IO方式,也叫AIO,
非同步IO操作基於事件和回撥機制,可以簡單理解為,應用操作直接返回,不會阻塞在那裡,當後臺處理完成,作業系統會通知相應執行緒進行後 續工作
BIO,NIO,AIO 特點:
BIO:該方式適用於數目比較小且固定的架構,這種方式對於伺服器資源要求比較高,併發侷限於應用中,是JDK1.4以前唯一的選擇,但程式直觀簡單易理解
NIO: 該方式適用於數目比較多且連線比較短(輕操作)的架構,比如聊天伺服器,併發侷限於應用中,程式設計比較複雜,JDK1.4以後支援
AIO: 適用於數目比較多且連線比較長(重操作)的架構,比如相簿伺服器,充分呼叫OS參加併發操作,程式設計比較複雜,JDK1.7開始支援