理解J.U.C中的ReentrantLock
JUC是啥?
其實很簡單,大家都喜歡縮寫!J.U.C= java.util.concurrent就是這個東西
來自哪裡?出現的理由
在Lock介面出現之前,java中的應用程式對於多執行緒的併發安全處理只能基於synchronized關鍵字來解決。但是synchronized在有些場景中會存在一些短板,也就是它並不適合所有的併發場景。但是在java5以後,Lock的出現可以解決synchronized在某些場景中的短板,它比synchronized更加靈活
下面我們來簡單介紹幾種鎖:
- 1、ReentrantLock(重入鎖)
- 2、ReentrantReadWriteLock(重入讀寫鎖)
看下面的案例: ReentrantLock的Demo
public class ReentrantLockTest1 {
static int value = 0;
Lock lock = new ReentrantLock();
public static void incr() {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
value++;
}
public static void main(String[] args) {
Thread[] threads = new Thread[1000];
for (int i = 0; i < 1000; i++) {
threads[i] = new Thread(() -> {
incr();
});
}
// 啟動執行緒
for (int i = 0; i < 1000; i++) {
threads[i].start();
}
System.out.println("value的值為:" + value);
}
}
結果: value的值為:960
很明顯這個結果不是我們想要的!我們想要的是: 1000
繼續往下看:
public class ReentrantLockTest1 {
static int value = 0;
static Lock lock = new ReentrantLock();
public static void incr() {
try {
lock.lock();
value ++;
try {
Thread.sleep(1);
value++;
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Thread[] threads = new Thread[1000];
for (int i = 0; i < 1000; i++) {
threads[i] = new Thread(() -> {
incr();
});
}
// 啟動執行緒
for (int i = 0; i < 1000; i++) {
threads[i].start();
}
System.out.println("value的值為:" + value);
}
}
結果: value的值為:89
說明什麼?完整獲取鎖的執行只有89次,我們在改變一下
接著看下面的案例:
public class ReentrantLockTest1 {
static int value = 0;
static Lock lock = new ReentrantLock();
public static void incr() {
try {
lock.lock();
value++;
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[1000];
for (int i = 0; i < 1000; i++) {
threads[i] = new Thread(() -> {
incr();
});
}
// 啟動執行緒
for (int i = 0; i < 1000; i++) {
threads[i].start();
}
Thread.sleep(3000);
System.out.println("value的值為:" + value);
}
}
結果: value的值為:1000
以上得出的結論是: ReentrantLock.lock() 確實可以保證多執行緒情況下的執行緒安全,前提是你得讓他執行完!
在上面執行的工程中我們發現一個問題我們嘗試過用ReentrantLock.tryLock() 去嘗試獲得鎖,但是存在一個問題:
public class ReentrantLockTest1 {
static int value = 0;
static Lock lock = new ReentrantLock();
public static void incr() {
if (lock.tryLock()) {
value++;
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[1000];
for (int i = 0; i < 1000; i++) {
threads[i] = new Thread(() -> {
incr();
});
}
// 啟動執行緒
for (int i = 0; i < 1000; i++) {
threads[i].start();
}
Thread.sleep(10000);
System.out.println("value的值為:" + value);
}
}
前提: 我試過把睡眠時間調整為 3、7、7、10秒,但是得到的結果都是不足1000
這樣子說來
ReentrantLock.lock()
ReentrantLock.tryLock()
存在很大區別了
從結果上看:ReentrantLock.lock()最起碼能保證結果的正確性
ReentrantLock.tryLock()不能保證結果的正確性
我們先去看下ReentrantLock.tryLock()因為Lock()的底層原理我已經比較熟悉了
程式碼如下:
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
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;
}
ReentrantLock.lock()最起碼能保證結果的正確性的原因是:
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
它將為獲取到鎖的執行緒放置到了一個等待佇列(雙向連結串列)中
所以lock() tryLock() 從本質上講還是存在很大區別的!!!
下面我們再說下: ReentrantReadWriteLock(重入讀寫鎖)
看下面的案例:
public class Demo {
static Map<String, Object> cacheMap = new HashMap<>();
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock read = rwl.readLock();
static Lock write = rwl.writeLock();
static Lock fromLock = new ReentrantLock();
public static Object get(String key) {
if (fromLock.tryLock())
// 讀鎖 阻塞
read.lock();
try {
return cacheMap.get(key);
} finally {
read.unlock();
}
}
public static Object write(String key, Object value) {
// other thread 獲得了寫鎖
write.lock();
try {
return cacheMap.put(key, value);
} finally {
write.unlock();
}
}
}
說明: 當多個執行緒訪問get()/write()方法的時候,當多個執行緒讀一個變數的時候是不互斥的,但是當一個執行緒獲取了寫鎖,那麼此時
讀鎖會阻塞,防止拿到當資料
Ps: ReentrantReadWriteLock適用於讀多寫少的場景
但是!究竟尼瑪為啥,當獲取寫鎖的時候讀鎖會阻塞?我們去看看
/**
* A {@code ReadWriteLock} maintains a pair of associated {@link
* Lock locks}, one for read-only operations and one for writing.
* The {@link #readLock read lock} may be held simultaneously by
* multiple reader threads, so long as there are no writers. The
* {@link #writeLock write lock} is exclusive.
*/
我感覺已經說的很明顯了。。實際上是因為位置,沒有看到具體的實現
上面的問題呢?先放著吧,暫時超出我的能力,需要指引!!!
思考鎖的實現(設計思維)
1、鎖的互斥
2、沒有搶佔到鎖的執行緒?
3、等待的執行緒怎麼儲存?
4、公平和非公平(能否插隊)
5、重入的特性(識別是否同一個人?ThreadID)
解決方案:
1、鎖的互斥,說的在簡單點就是就共享資源的競爭,巧的是以前搶奪的是共享資源!現在搶佔的是一個標誌位!state,如果state=0那麼代表當前執行緒沒有搶佔到鎖,如果state=1則代表搶佔到了鎖,可以繼續向下執行
2、3 沒有搶佔到鎖的執行緒我們該如何處理?等待的執行緒怎麼儲存?我們可以舉例下面的一個場景,好比去醫院看病,這個例子不好!換一個~假如我們去洗腳城洗腳吧,我們中意7號!但是奈何喜歡她的人比較多,老闆只能讓你等著等7號空閒出來了,你才能上!用詞錯誤,你才能洗~ 但是,不可能說我先來的我最後一個上是吧,所以老闆需要給我發一個號碼牌,假定是9527號,按照正常來講一定是順序排隊的,誰先來,誰上!
4、這個公平不公平我們沿用上面的例子!正常來說一定是誰先來的誰先上,但是存在一個問題,一個新來的大哥,看隊伍比較長,他想先洗,不洗就掛了!拿500塊買我的位置~ 我可能也不會賣,除非給我550!如果我賣他了,那就是不公平的(大哥插隊了),如果我大喝一聲: 這世道竟然還有插隊的!?他可能就得老老實實排隊去了,那麼就是公平的,因為得排隊
5、重入性這個就比較有意思了~ 7號給大爺,再加個鍾!!,懂的都懂。。不能再說了
技術方案:
1、volatile state = 0;(無鎖), 1代表是持有鎖, > 1代表重入
2、wait/notify馬上到!condition 需要喚醒指定執行緒。【LockSupport.park(); -> unpark(thread)】 unsafe類中提供的一個方法
3、雙向連結串列
4、邏輯層面實現
5、在某一個地方儲存當前獲得鎖的執行緒的ID,判斷下次搶佔鎖的執行緒是否為同一個
下面我們來模擬一個場景: 模擬三個執行緒爭奪lock()的場景(先把總體的圖給你們,再去看原始碼分析)
/**
* Acquires the lock.
*
* <p>If the lock is not available then the current thread becomes
* disabled for thread scheduling purposes and lies dormant until the
* lock has been acquired.
*
* <p><b>Implementation Considerations</b>
*
* <p>A {@code Lock} implementation may be able to detect erroneous use
* of the lock, such as an invocation that would cause deadlock, and
* may throw an (unchecked) exception in such circumstances. The
* circumstances and the exception type must be documented by that
* {@code Lock} implementation.
*/
void lock();
說的什麼意思呢?
1、嘗試獲取鎖
2、在獲取鎖的過程中如果發現當前鎖沒搶到那麼,當前執行緒會變為阻塞狀態進入休眠狀態
3、當持有鎖的執行緒釋放掉鎖,那麼休眠的執行緒就可以去競爭鎖
/**
* Acquires the lock.
*
* <p>Acquires the lock if it is not held by another thread and returns
* immediately, setting the lock hold count to one.
*
* <p>If the current thread already holds the lock then the hold
* count is incremented by one and the method returns immediately.
*
* <p>If the lock is held by another thread then the
* current thread becomes disabled for thread scheduling
* purposes and lies dormant until the lock has been acquired,
* at which time the lock hold count is set to one.
*/
public void lock() {
sync.lock();
}
這個是ReentrantLock裡面的lock()說的什麼意思呢?
1、如果當前鎖沒有被持有那麼當前執行緒持有鎖,並且將持有次數設定為1
2、如果當前執行緒已經持有了鎖,那麼持有次數 + 1,並且立即返回表示持有鎖
3、同上
這個sync是啥?瞅一瞅
提供所有實現機制的同步器,基於AQS去表示當前鎖的狀態,成吧(我是沒理解)
說下我的理解吧
保證鎖狀態"state"的實時性,這東西就是幹這個的!
我們接著看非公平鎖的實現
/**
* Sync object for non-fair locks
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
分析: (我們多分析點,多個執行緒搶奪鎖的情況,分析如圖的情況吧ThreadA、ThreadB、ThreadC)
第一次,剛剛進入,此時state = 0, 那麼我們進入if分支
setExclusiveOwnerThread(Thread.currentThread());
註釋如下:
/**
* Sets the thread that currently owns exclusive access.
* A {@code null} argument indicates that no thread owns access.
* This method does not otherwise impose any synchronization or
* {@code volatile} field accesses.
* @param thread the owner thread
*/
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
/**
* The current owner of exclusive mode synchronization.
*/
private transient Thread exclusiveOwnerThread;
解釋:
它說了一堆沒有的沒用的,總結就是一句話: 表示當前這個執行緒擁有了鎖,可以去訪問了!沒了。
總結: 第一次進入做了什麼事呢?
1、設定state 0 ---> 1
2、設定exclusiveOwnerThread 為當前執行緒
(我畫的圖還是蠻好的!!!)
那麼當一個執行緒持有鎖,其他執行緒進入是什麼樣子的一個情況呢?我們繼續分析
它會進入else分支,那麼如下:
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
註解解釋:
忽略打斷,至少呼叫一次tryAcquire(),嘗試去獲取鎖
換句話說,執行緒在一個佇列中可能被再次阻塞和釋放,不斷呼叫tryAcquire()
方法直到成功,該方法被呼叫一般在實現了Lock介面(聽不出什麼東西),不過可以知曉下面兩點:
1、阻塞的執行緒在佇列中
2、阻塞的執行緒會呼叫tryAcquire()方法
我們再來仔細分析下acquire(int arg),這裡面呼叫了什麼方法,呵~好傢伙,可不少
1、tryAcquire(arg)
2、addWaiter(Node.EXCLUSIVE)
3、acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
三個方法,我們一個一個來分析
1、tryAcquire(arg),其實在分析它前我們可以猜一下這個方法幹了什麼?
A、檢視當前的state是否變為了0,如果為零了,那麼就返回
養成好習慣,看原始碼前要先讀註釋,要先在總體上有一個把握,再去看具體的實現,不然,你看個什麼玩意,聽話養成好習慣,別看一大串子,別急,原始碼急不來的
差距就是在一點一滴中養成的
/**
* Attempts to acquire in exclusive mode. This method should query
* if the state of the object permits it to be acquired in the
* exclusive mode, and if so to acquire it.
*
* <p>This method is always invoked by the thread performing
* acquire. If this method reports failure, the acquire method
* may queue the thread, if it is not already queued, until it is
* signalled by a release from some other thread. This can be used
* to implement method {@link Lock#tryLock()}.
*
* <p>The default
* implementation throws {@link UnsupportedOperationException}.
*
* @param arg the acquire argument. This value is always the one
* passed to an acquire method, or is the value saved on entry
* to a condition wait. The value is otherwise uninterpreted
* and can represent anything you like.
* @return {@code true} if successful. Upon success, this object has
* been acquired.
* @throws IllegalMonitorStateException if acquiring would place this
* synchronizer in an illegal state. This exception must be
* thrown in a consistent fashion for synchronization to work
* correctly.
* @throws UnsupportedOperationException if exclusive mode is not supported
*/
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
我們來一起讀,其實我也沒看過這裡,也是新的知識,這是我的學習方法,我感覺還不錯吧
1、嘗試去獲取獨佔模式(也就是去獲取這個鎖)
2、當state 准許被訪問的時候,訪問這個方法的執行緒應該是有序的排隊訪問
3、如果說執行緒沒有獲取到state那麼它可能會進等待佇列中,如果它沒有在等待佇列中話(這裡面是有說法的 a、等待佇列中的執行緒去順序獲取state b、未在佇列中的也可以競爭)
4、以上的所有前提是: signalled by a release(state)
Ps: 其實說的已經很明顯了!你看我們上面的圖,沒有獲取到鎖的執行緒,它會進入到一個雙向的等待佇列中
繼續往下看:
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
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;
}
這個方法比較簡單:
其實,這個方法要明確一個前提就是,我們可以嘗試著去獲取鎖了!(此時鎖可能還未釋放)
1、如果搶佔到了則獲取state,並設定執行緒為自己
2、如果獲取state的執行緒為當前持有state的執行緒,那麼重入次數 + 1
下面我們來分析第二個方法: addWaiter(Node.EXCLUSIVE), arg)
這個中規中矩,其實還可以吧
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
解釋:
新增節點至雙向佇列,節點以給定的模式進行儲存,如果當前佇列存在節點,那麼進入if分支,如果不存在節點那麼走非if分支,我們接著看這兩個分支
我們這個先進入enq(node);這個方法
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
解釋: 可以看出以下知識點:
1、尾插法 Node t = tail
2、當佇列中不存在元素的時候那麼tail = head = new Node
3、else 分支node.prev = t其實執行的操作就是新插入元素的前一個元素為原佇列的尾節點,那麼可以判斷
新插入的元素必定為佇列的尾節點
4、我們看下compareAndSetTail(t, node),應該指的就是我們上面的操作,點進去之後發現是一個native方法,但是可以推測和我們猜測差不多的
5、compareAndSetHead(new Node()) 這個方法點進去也是native的至於功能我們也闡述過了
Ps: 再來看下我們的圖: 沒有獲得鎖的執行緒,是不是很神奇
我們接著往下看,第三個方法: 是以第二個方法返回的Node作為引數
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
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);
}
}
解釋下:
通俗點解釋就是,將這個未獲取到鎖的Node丟到等待佇列中,當鎖可以被競爭了"state"那麼他就活了
/**
* Returns previous node, or throws NullPointerException if null.
* Use when predecessor cannot be null. The null check could
* be elided, but is present to help the VM.
*
* @return the predecessor of this node
*/
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
返回當前節點的前一個節點
繼續研究: final boolean acquireQueued(final Node node, int arg)
那麼如下: 說了啥呢
1、如果當前節點的前一個節點為頭結點並且嘗試獲取鎖成功!那麼將node設定為當前等待佇列的head節點
2、如果不成立的話,說明當前鎖還是不可獲取的狀態這時判斷是否可以掛起當前執行緒、
3、如果判斷結果為真則掛起當前執行緒, 否則繼續迴圈,
4、在這期間執行緒不響應中斷
5、在最後確保如果獲取失敗就取消獲取
if (failed) {
cancelAcquire(node);
}
我目前的水平值准許我分析到這種程度了。。以後找到物件我再繼續分析,哈哈! 再見。
有問題,大家一起討論,不開心你罵我也成,但是你得說出所以然,不然我可能會去打你。。
圖感覺有點花,可以看這個: https://app.yinxiang.com/fx/279855bd-bcda-462e-be8f-e69ab987df95
相關文章
- 深入理解 ReentrantLockReentrantLock
- 徹底理解ReentrantLockReentrantLock
- 【Interview】深入理解ReentrantLockViewReentrantLock
- 深入理解ReentrantLock的實現原理ReentrantLock
- 【漫畫】JAVA併發程式設計 J.U.C Lock包之ReentrantLock互斥鎖Java程式設計ReentrantLock
- Java中的ReentrantLock鎖JavaReentrantLock
- 簡單聊聊Java中的ReentrantLockJavaReentrantLock
- 美團後臺篇中的ReentrantLockReentrantLock
- Java併發程式設計,深入理解ReentrantLockJava程式設計ReentrantLock
- 面試中如何答好:ReentrantLock面試ReentrantLock
- J.U.C - AQSAQS
- 【Java】深入理解ReentrantLock可重入鎖之簡單使用JavaReentrantLock
- Java 併發程式設計(十) -- ReentrantLock中的SyncJava程式設計ReentrantLock
- 死磕java concurrent包系列(三)基於ReentrantLock理解AQS的條件佇列JavaReentrantLockAQS佇列
- ReentrantLock 使用ReentrantLock
- ReentrantLock類ReentrantLock
- ReentrantLock & AQSReentrantLockAQS
- Java 併發程式設計(十一) -- ReentrantLock中的公平鎖FairSyncJava程式設計ReentrantLockAI
- Synchronized 與 ReentrantLock 的區別synchronizedReentrantLock
- synchronized與ReentrantLock的區別synchronizedReentrantLock
- ReentrantLock的條件佇列ReentrantLock佇列
- 淺析 ReentrantLockReentrantLock
- ReentrantLock詳解ReentrantLock
- ReentrantLock原理分析ReentrantLock
- AtomicBoolean與ReentrantLockBooleanReentrantLock
- ReentrantLock原始碼ReentrantLock原始碼
- 理解 JavaScript 中的 thisJavaScript
- 理解JS中的thisJS
- Java 併發程式設計(十二) -- ReentrantLock中的非公平鎖NonfairSyncJava程式設計ReentrantLockAI
- Java synchronized與ReentrantLock的區別JavasynchronizedReentrantLock
- J.U.C之Executor框架入門指引框架
- 併發容器J.U.C -- AQS元件(一)AQS元件
- 關於 ReentrantLock 中鎖 lock() 和解鎖 unlock() 的底層原理淺析ReentrantLock
- ReentrantLock原始碼解析ReentrantLock原始碼
- ReentrantLock實現原理ReentrantLock
- Java併發——ReentrantLockJavaReentrantLock
- ReentrantLock原始碼分析ReentrantLock原始碼
- 06 ReentrantLock之ConditionReentrantLock