ReentrantLock基礎
- ReentrantLock入門
- 基礎使用
- 公平鎖FairSync與非公平鎖NonfairSync
- 公共代碼解讀
- NonfairSync 核心代碼解讀
- FairSync核心代碼解讀
- LocksSuport
- LocksSuport與Object的wait、notify區別
- AQS 個人理解
- AQS特性
- 常見應用
- 常見問題
- 執行緒的 run() 和 start() 有什么區別?
- yiled方法說明
- synchronize的原理
- synchronize的膨脹程序
- synchronize粗化、消除

ReentrantLock入門
ReentrantLock是一種基于AQS框架的實作,也是JDK1.5后的一種執行緒并發訪問的同步手段,它類似于synchronized是一種互斥鎖,可以保證執行緒安全,它比synchronized更多的特性,比如它支持加鎖的公平性、支持手動加鎖與解鎖,
基礎使用
import java.util.concurrent.locks.ReentrantLock;
/**
* @program: 多執行緒測驗
* @description:
* @author: cc百川 https://blog.csdn.net/ljcc122/
* @create: 2021-04-10 13:52
**/
public class Test1_UserLock {
private static int count = 0;
public static void main(String[] args) throws InterruptedException {
ReentrantLock rlock = new ReentrantLock();//*1* 默認申請非公平鎖[^1] NonfairSync
for(int i=0;i<10;i++) {
new Thread(()->{
doRun(rlock);
}, i+"").start();
}
Thread.sleep(1000);
System.out.println(count);
}
/**
* 執行緒run方法內部邏輯
* @param rlock
*/
public static void doRun(ReentrantLock rlock){
rlock.lock();//*2*鎖競爭標識
//獲得鎖后進入下面的邏輯,沒有獲取鎖的進入自旋等待當前鎖釋放*3*
try{
for(int i=0;i<100;i++)
count++;
} finally {
rlock.unlock();//用完后釋放鎖,這也就是與synchronized的區別之一需要顯示的加釋鎖
}
}
}
公平鎖FairSync與非公平鎖NonfairSync
公平鎖簡單理解就是不區分身份,每一個任務都需要排隊,比如:高考進場,不區分你是班長還是課代表,都需要排隊進場,
非公平鎖簡單理解就是,任務由高低可以插隊,比如:銀行VIP機制,VIP不管后面有沒有排隊都插隊,

公共代碼解讀
//加鎖核心
public final void acquire(int arg) {
//嘗試獲取鎖,獲取失敗則自旋進入同步佇列,并阻塞自己 LockSupport.park(this);
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))//AQS核心同步等待CLH佇列(雙向鏈表) 中已Node的方式存盤
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) //設定當前節點的前節點的waitStatus=-1 用于標記是否能喚醒當前節點
&& parkAndCheckInterrupt())//阻塞
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//釋放核心
//釋放鎖
public void unlock() {
sync.release(1);
}
//嘗試釋放資源
public final boolean release(int arg) {
if (tryRelease(arg)) {//嘗試釋放資源就是設定state=0,并通知CLH佇列,可以進行鎖競爭了
Node h = head;
if (h != null && h.waitStatus != 0)//這里或判斷佇列的第一個節點waitStatus 狀態是否不等于0,不等于0說明可以喚醒佇列中的頭節點的下一個節點
unparkSuccessor(h);//拿到頭節點下一個節點的執行緒,重置下一個節點的資料含設定下一個節點waitStatus=0,頭節點后移自旋處會修改為-1
return true;
}
return false;
}
NonfairSync 核心代碼解讀
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
設定加鎖步驟:
第一步:直接嘗試cas設定鎖搶占,如果搶占成功就執行當前任務,
*/
final void lock() {
if (compareAndSetState(0, 1))//直接引數鎖的搶占
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);//嘗試獲取資源,成功則回傳true,失敗則回傳false
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();//AQS狀態標記 等于0表示當前沒有執行的任務,可以繼續執行任務
if (c == 0) {//等于0標識可以進行鎖的爭奪
if (compareAndSetState(0, acquires)) {//通過CAS爭奪鎖,爭奪成功執行自己的執行緒任務
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//可重入標志,如果當前執行緒是正在執行任務的執行緒,再次加鎖不需要爭奪鎖資源,給AQS的狀態為+acquires【這里為1】即可,這里也不用考慮執行緒安全問題,能執行任務的只有一個執行緒,
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
FairSync核心代碼解讀
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);//嘗試獲取資源,成功則回傳true,失敗則回傳false
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&//這里多了一步判斷因為公平鎖需要判斷只有佇列中的第一個才能最先去獲取鎖資源
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {//道理同非公平鎖相同代碼段
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
//判斷當前佇列是否為慷訓者當前執行緒是佇列第一個
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
LocksSuport
指定執行緒的阻塞與喚醒,
內部使用Unsafe的park阻塞、喚醒執行緒,
public class Test2_LockSuport {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
LockSupport.park();//阻塞當前執行緒
System.out.println(Thread.currentThread().getName());
}, "cc百川lockA");
Thread t2 = new Thread(()->{
LockSupport.park();//阻塞當前執行緒
System.out.println(Thread.currentThread().getName());
}, "cc百川lockB");
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println("主執行緒喚醒");
LockSupport.unpark(t1);//主執行緒喚醒t1執行緒
}
}
結果
LocksSuport與Object的wait、notify區別
-
共同點
LockSupport中的park方法和Object中的wait方法都可以使執行緒進入WAIT或者TIMED_WAIT狀態
LockSupport中的unpark方法和Object中的notify可以使執行緒脫離WAIT、TIMED_WAIT狀態 -
不同點
Object中的wait方法在呼叫時當前執行緒必須要對該Object進行加鎖
Object中wait和notify方法必須要按順序呼叫,
Object中notify不能指定喚醒哪一個執行緒,之鞥呢通過notifyAll喚醒所有執行緒,而LockSupport.ubpark(Thread)可以喚醒指定執行緒,
notify()隨機喚醒一個執行緒,notifyAll()會喚醒所有的執行緒
喚醒的執行緒由等待池進入執行緒鎖池中,參與 鎖的競爭,競爭成功則執行,如不成功則停 留在鎖池中等待鎖被釋放后,再參與競爭
注意:wait與Thread.sleep區別參考:多執行緒系列:Java創建執行緒的方式
AQS 個人理解
AQS(AbstractQueuedSynchronizer)主要利用 CAS (Compare AndSwap)和volatile和native方法來保證原子操作, 從而避免synchronized的高開銷,執行效率大為提升,基于FIFO的CLH佇列保證了同步阻塞,Unsafe請參考:多執行緒系列:阻塞佇列BlockingQueue分析
AQS特性
重要volatile變數 state記錄當前是否存在執行緒在執行任務,(state>0)
1.阻塞等待佇列
2.獨占、共享
3.公平與非公平
4.允許中斷
5.可重入
常見應用
- ReentrantLock:是一個獨占模式的執行緒安全鎖,
- ReentrantReadWriteLock:讀鎖共享鎖S,寫鎖排他鎖X
- Semaphore:通過共享的方式,主要是通過記錄信號量的方式,等待有信號量才能執行
- CountDownLatch:通過共享的方式,通過計數的方式,等待其他執行緒完成后在繼續執行,當計數器為0時表示任務執行完畢,恢復阻塞的執行緒
常見問題
執行緒的 run() 和 start() 有什么區別?
start用于啟動執行緒,可以進入執行緒的生命周期,start只能呼叫一次,
run方法用于指向執行緒的運行時代碼,run可以重復呼叫(類似普通方法),
yiled方法說明
讓當前正在運行的執行緒回到可運行狀態,允許相同優先級的其他執行緒,參與鎖競爭運行的機會,
synchronize的原理
synchronized是由一對monitorenter/monitorexit指令實作的, monitor物件是同步的基本實作單

synchronize的膨脹程序
主要是說的JDK1.6之后
無鎖->偏向鎖->輕量級鎖->重量級鎖【注意鎖的膨脹不可逆】即升級后不能退回到上一個狀態
| 鎖型別 | 原因 |
|---|---|
| 偏向鎖 | 無實際競爭,且自由一個執行緒獲得鎖的全部使用 |
| 輕量級鎖 | 無實際競爭,多個執行緒交替使用鎖,運行短時間鎖的競爭 |
| 重量級鎖 | 有實際競爭,且鎖競爭時間較長 |
synchronize粗化、消除
需要開啟JVM逃逸分析【其他作用標量替換,把物件分配到堆疊中,減少GC次數】
| 型別 | 說明 | 圖解 |
|---|---|---|
| 粗化 | 指把同一個物件的多個鎖粗化為一個鎖,在同一個方法中多次對同一個物件加鎖,理論上是無意義的,其他執行緒獲取了當前鎖 | ![]() |
| 消除 | 指把無用鎖消除,這里在方法類加鎖且物件不存在競爭即可以不加 | ![]() |
持續完善中,望各位看官指導
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/275136.html
標籤:java
上一篇:Java 基礎
下一篇:設計模式:行為型-迭代器模式


