★ 1、講講 Lock 鎖
是一個接口,有三個實作類,分别是常用的
可重入鎖,讀鎖、寫鎖
。常用的是可重入鎖。
加鎖使用lock() 方法,解鎖使用 unlock() 方法。Lock的底層是 AQS+CAS機制 實作。
Lock 常用子類 可重入鎖ReentrantLock 有兩種模式,
公平鎖模式、非公平鎖模式
和
公平鎖模式
的應用
非公平鎖模式
- 預設一般建立的是非公平鎖,就是允許線程插隊,而不是按先來後到順序
- 并發量高的,非公平可能會導緻線程餓死 === 做中間件,比如rocketmq 就需要關注鎖公平和不公平
可重入鎖的意思是 對于同一線程可以重複去擷取鎖。應用場景--遞歸,例如檔案夾周遊目錄下的所有檔案名。
- mq 消息隊列的應用,比如網易雲多個使用者的評論->mq->如果是非公平鎖,那麼導緻線程饑餓,導緻等待時間過長-不穩定
解決:mq源碼的queue包下有:
RoundQueue(線程不安全),ConcurrentTreeMap(線程安全-put 方法使用了lock 加鎖,且lock = new ReentrantLock(true);)
★ 2、和synchronized 的使用差別/ 說說lock 和 synchronized 鎖的差別
- synchronized 是一個
,使用C++實作的,沒辦法控制鎖的開始、鎖結束,也沒辦法中斷線程的執行關鍵字
- 而 lock 是
,可以擷取鎖的狀态,開啟鎖,釋放鎖,通過設定可以中斷線程的執行,更加靈活java層面的實作
- 是否自動是否鎖:synchronized 會自動是否鎖,而 lock 需要手動調用unlock 方法釋放,否則會死循環
lock.lock();//其他沒有拿到鎖的線程?阻塞 卡着不動
boolean res = lock.tryLock(1000, TimeUnit.MILLISECONDS);//一秒之後如果沒有拿到鎖,就傳回false
lock.lockInterruptibly();//中斷方法
★ 3、講講 trylock、lock方法
lock 鎖設計上的核心成員:鎖狀态、鎖擁有者、等待隊列
源碼方面:在 ReentrantLock 中 使用了關鍵成員是同步器AQS(源碼中的Sync)
trylock方法:擷取鎖/是否鎖成功
- 鎖的狀态,0 代表未占用鎖,大于0 則代表占用鎖的次數。
- 首先目前線程以CAS的方式,嘗試将鎖的狀态從0修改成1,就是嘗試擷取鎖。
- 擷取到了就把目前線程設定給AQS的屬性exclusiveOwnerThread,也就是指明目前鎖的擁有者是目前線程。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//如果占用鎖的是目前線程,則代表重入次數
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
lock方法:加鎖
- 非公平鎖模式,首先目前線程以CAS的方式,嘗試将鎖的狀态從0修改成1,就是嘗試擷取鎖。
- 擷取到了就把目前線程設定給AQS的屬性exclusiveOwnerThread,也就是指明目前鎖的擁有者是目前線程。
- 目前鎖已經被占用,線程會進入等待隊列,不斷地搶鎖,搶到鎖直接從等待隊列彈出,否則判斷線程的狀态是否需要挂起(阻塞),這裡循環搶鎖,不斷調用了嘗試擷取鎖的方法,也利用了CAS思想。
// 非公平鎖模式 lock = new ReentrantLock();
final void lock() {
// 首先以 CAS 的方式,嘗試将state 從0修改成1
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
// compareAndSetState(0, 1)--> CAS 機制
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
// acquire(1);--> CAS 機制
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//進入等待隊列,繼續不斷嘗試擷取鎖,直到搶到鎖則彈出隊列,否則判斷線程的狀态是否需要挂起
selfInterrupt();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())//判斷線程的狀态是否需要挂起
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}