'Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的'

Java 設計 文章 IT小米 2019-09-12
"
"
Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

導讀(需要Java相關學習資料,轉發私信小編“學習”

在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下:

1.公平鎖 / 非公平鎖

2.可重入鎖 / 不可重入鎖

3.獨享鎖 / 共享鎖

4.互斥鎖 / 讀寫鎖

5.樂觀鎖 / 悲觀鎖

6.分段鎖

7.偏向鎖 / 輕量級鎖 / 重量級鎖

8.自旋鎖

上面是很多鎖的名詞,這些分類並不是全是指鎖的狀態,有的指鎖的特性,有的指鎖的設計,下面總結的內容是對每個鎖的名詞進行一定的解釋。

公平鎖/非公平鎖

需要Java相關學習資料,轉發私信小編“java”

公平鎖

公平鎖是指多個線程按照申請鎖的順序來獲取鎖。

非公平鎖

非公平鎖是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。有可能,會造成優先級反轉或者飢餓現象。

對於Java ReentrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在於吞吐量比公平鎖大。

對於Synchronized而言,也是一種非公平鎖。由於其並不像ReentrantLock是通過AQS的來實現線程調度,所以並沒有任何辦法使其變成公平鎖。

可重入鎖/不可重入鎖

"
Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

導讀(需要Java相關學習資料,轉發私信小編“學習”

在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下:

1.公平鎖 / 非公平鎖

2.可重入鎖 / 不可重入鎖

3.獨享鎖 / 共享鎖

4.互斥鎖 / 讀寫鎖

5.樂觀鎖 / 悲觀鎖

6.分段鎖

7.偏向鎖 / 輕量級鎖 / 重量級鎖

8.自旋鎖

上面是很多鎖的名詞,這些分類並不是全是指鎖的狀態,有的指鎖的特性,有的指鎖的設計,下面總結的內容是對每個鎖的名詞進行一定的解釋。

公平鎖/非公平鎖

需要Java相關學習資料,轉發私信小編“java”

公平鎖

公平鎖是指多個線程按照申請鎖的順序來獲取鎖。

非公平鎖

非公平鎖是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。有可能,會造成優先級反轉或者飢餓現象。

對於Java ReentrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在於吞吐量比公平鎖大。

對於Synchronized而言,也是一種非公平鎖。由於其並不像ReentrantLock是通過AQS的來實現線程調度,所以並沒有任何辦法使其變成公平鎖。

可重入鎖/不可重入鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

可重入鎖

廣義上的可重入鎖指的是可重複可遞歸調用的鎖,在外層使用鎖之後,在內層仍然可以使用,並且不發生死鎖(前提得是同一個對象或者class),這樣的鎖就叫做可重入鎖。ReentrantLock和synchronized都是可重入鎖

synchronized void setA() throws Exception{

Thread.sleep(1000);

setB();

}

synchronized void setB() throws Exception{

Thread.sleep(1000);

}

上面的代碼就是一個可重入鎖的一個特點,如果不是可重入鎖的話,setB可能不會被當前線程執行,可能造成死鎖。

不可重入鎖

不可重入鎖,與可重入鎖相反,不可遞歸調用,遞歸調用就發生死鎖。看到一個經典的講解,使用自旋鎖來模擬一個不可重入鎖,代碼如下:

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

public void lock() {

Thread current = Thread.currentThread();

//這句是很經典的“自旋”語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

owner.compareAndSet(current, null);

}

}

代碼也比較簡單,使用原子引用來存放線程,同一線程兩次調用lock()方法,如果不執行unlock()釋放鎖的話,第二次調用自旋的時候就會產生死鎖,這個鎖就不是可重入的,而實際上同一個線程不必每次都去釋放鎖再來獲取鎖,這樣的調度切換是很耗資源的。

把它變成一個可重入鎖

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

private int state = 0;

public void lock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

state++;

return;

}

//這句是很經典的“自旋”式語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

if (state != 0) {

state--;

} else {

owner.compareAndSet(current, null);

}

}

}

}

在執行每次操作之前,判斷當前鎖持有者是否是當前對象,採用state計數,不用每次去釋放鎖。

需要Java相關學習資料,轉發私信小編“java”

ReentrantLock中可重入鎖實現

這裡看非公平鎖的鎖獲取方法:

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;

}

在AQS中維護了一個private volatile int state來計數重入次數,避免了頻繁的持有釋放操作,這樣既提升了效率,又避免了死鎖。

獨享鎖/共享鎖

"
Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

導讀(需要Java相關學習資料,轉發私信小編“學習”

在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下:

1.公平鎖 / 非公平鎖

2.可重入鎖 / 不可重入鎖

3.獨享鎖 / 共享鎖

4.互斥鎖 / 讀寫鎖

5.樂觀鎖 / 悲觀鎖

6.分段鎖

7.偏向鎖 / 輕量級鎖 / 重量級鎖

8.自旋鎖

上面是很多鎖的名詞,這些分類並不是全是指鎖的狀態,有的指鎖的特性,有的指鎖的設計,下面總結的內容是對每個鎖的名詞進行一定的解釋。

公平鎖/非公平鎖

需要Java相關學習資料,轉發私信小編“java”

公平鎖

公平鎖是指多個線程按照申請鎖的順序來獲取鎖。

非公平鎖

非公平鎖是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。有可能,會造成優先級反轉或者飢餓現象。

對於Java ReentrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在於吞吐量比公平鎖大。

對於Synchronized而言,也是一種非公平鎖。由於其並不像ReentrantLock是通過AQS的來實現線程調度,所以並沒有任何辦法使其變成公平鎖。

可重入鎖/不可重入鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

可重入鎖

廣義上的可重入鎖指的是可重複可遞歸調用的鎖,在外層使用鎖之後,在內層仍然可以使用,並且不發生死鎖(前提得是同一個對象或者class),這樣的鎖就叫做可重入鎖。ReentrantLock和synchronized都是可重入鎖

synchronized void setA() throws Exception{

Thread.sleep(1000);

setB();

}

synchronized void setB() throws Exception{

Thread.sleep(1000);

}

上面的代碼就是一個可重入鎖的一個特點,如果不是可重入鎖的話,setB可能不會被當前線程執行,可能造成死鎖。

不可重入鎖

不可重入鎖,與可重入鎖相反,不可遞歸調用,遞歸調用就發生死鎖。看到一個經典的講解,使用自旋鎖來模擬一個不可重入鎖,代碼如下:

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

public void lock() {

Thread current = Thread.currentThread();

//這句是很經典的“自旋”語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

owner.compareAndSet(current, null);

}

}

代碼也比較簡單,使用原子引用來存放線程,同一線程兩次調用lock()方法,如果不執行unlock()釋放鎖的話,第二次調用自旋的時候就會產生死鎖,這個鎖就不是可重入的,而實際上同一個線程不必每次都去釋放鎖再來獲取鎖,這樣的調度切換是很耗資源的。

把它變成一個可重入鎖

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

private int state = 0;

public void lock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

state++;

return;

}

//這句是很經典的“自旋”式語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

if (state != 0) {

state--;

} else {

owner.compareAndSet(current, null);

}

}

}

}

在執行每次操作之前,判斷當前鎖持有者是否是當前對象,採用state計數,不用每次去釋放鎖。

需要Java相關學習資料,轉發私信小編“java”

ReentrantLock中可重入鎖實現

這裡看非公平鎖的鎖獲取方法:

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;

}

在AQS中維護了一個private volatile int state來計數重入次數,避免了頻繁的持有釋放操作,這樣既提升了效率,又避免了死鎖。

獨享鎖/共享鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

獨享鎖和共享鎖在你去讀C.U.T包下的ReeReentrantLock和ReentrantReadWriteLock你就會發現,它倆一個是獨享一個是共享鎖。

獨享鎖:該鎖每一次只能被一個線程所持有。

共享鎖:該鎖可被多個線程共有,典型的就是ReentrantReadWriteLock裡的讀鎖,它的讀鎖是可以被共享的,但是它的寫鎖確每次只能被獨佔。

另外讀鎖的共享可保證併發讀是非常高效的,但是讀寫和寫寫,寫讀都是互斥的。

獨享鎖與共享鎖也是通過AQS來實現的,通過實現不同的方法,來實現獨享或者共享。

對於Synchronized而言,當然是獨享鎖。

互斥鎖/讀寫鎖

"
Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

導讀(需要Java相關學習資料,轉發私信小編“學習”

在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下:

1.公平鎖 / 非公平鎖

2.可重入鎖 / 不可重入鎖

3.獨享鎖 / 共享鎖

4.互斥鎖 / 讀寫鎖

5.樂觀鎖 / 悲觀鎖

6.分段鎖

7.偏向鎖 / 輕量級鎖 / 重量級鎖

8.自旋鎖

上面是很多鎖的名詞,這些分類並不是全是指鎖的狀態,有的指鎖的特性,有的指鎖的設計,下面總結的內容是對每個鎖的名詞進行一定的解釋。

公平鎖/非公平鎖

需要Java相關學習資料,轉發私信小編“java”

公平鎖

公平鎖是指多個線程按照申請鎖的順序來獲取鎖。

非公平鎖

非公平鎖是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。有可能,會造成優先級反轉或者飢餓現象。

對於Java ReentrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在於吞吐量比公平鎖大。

對於Synchronized而言,也是一種非公平鎖。由於其並不像ReentrantLock是通過AQS的來實現線程調度,所以並沒有任何辦法使其變成公平鎖。

可重入鎖/不可重入鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

可重入鎖

廣義上的可重入鎖指的是可重複可遞歸調用的鎖,在外層使用鎖之後,在內層仍然可以使用,並且不發生死鎖(前提得是同一個對象或者class),這樣的鎖就叫做可重入鎖。ReentrantLock和synchronized都是可重入鎖

synchronized void setA() throws Exception{

Thread.sleep(1000);

setB();

}

synchronized void setB() throws Exception{

Thread.sleep(1000);

}

上面的代碼就是一個可重入鎖的一個特點,如果不是可重入鎖的話,setB可能不會被當前線程執行,可能造成死鎖。

不可重入鎖

不可重入鎖,與可重入鎖相反,不可遞歸調用,遞歸調用就發生死鎖。看到一個經典的講解,使用自旋鎖來模擬一個不可重入鎖,代碼如下:

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

public void lock() {

Thread current = Thread.currentThread();

//這句是很經典的“自旋”語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

owner.compareAndSet(current, null);

}

}

代碼也比較簡單,使用原子引用來存放線程,同一線程兩次調用lock()方法,如果不執行unlock()釋放鎖的話,第二次調用自旋的時候就會產生死鎖,這個鎖就不是可重入的,而實際上同一個線程不必每次都去釋放鎖再來獲取鎖,這樣的調度切換是很耗資源的。

把它變成一個可重入鎖

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

private int state = 0;

public void lock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

state++;

return;

}

//這句是很經典的“自旋”式語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

if (state != 0) {

state--;

} else {

owner.compareAndSet(current, null);

}

}

}

}

在執行每次操作之前,判斷當前鎖持有者是否是當前對象,採用state計數,不用每次去釋放鎖。

需要Java相關學習資料,轉發私信小編“java”

ReentrantLock中可重入鎖實現

這裡看非公平鎖的鎖獲取方法:

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;

}

在AQS中維護了一個private volatile int state來計數重入次數,避免了頻繁的持有釋放操作,這樣既提升了效率,又避免了死鎖。

獨享鎖/共享鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

獨享鎖和共享鎖在你去讀C.U.T包下的ReeReentrantLock和ReentrantReadWriteLock你就會發現,它倆一個是獨享一個是共享鎖。

獨享鎖:該鎖每一次只能被一個線程所持有。

共享鎖:該鎖可被多個線程共有,典型的就是ReentrantReadWriteLock裡的讀鎖,它的讀鎖是可以被共享的,但是它的寫鎖確每次只能被獨佔。

另外讀鎖的共享可保證併發讀是非常高效的,但是讀寫和寫寫,寫讀都是互斥的。

獨享鎖與共享鎖也是通過AQS來實現的,通過實現不同的方法,來實現獨享或者共享。

對於Synchronized而言,當然是獨享鎖。

互斥鎖/讀寫鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

互斥鎖

在訪問共享資源之前對進行加鎖操作,在訪問完成之後進行解鎖操作。 加鎖後,任何其他試圖再次加鎖的線程會被阻塞,直到當前進程解鎖。

如果解鎖時有一個以上的線程阻塞,那麼所有該鎖上的線程都被編程就緒狀態, 第一個變為就緒狀態的線程又執行加鎖操作,那麼其他的線程又會進入等待。 在這種方式下,只有一個線程能夠訪問被互斥鎖保護的資源

讀寫鎖

讀寫鎖既是互斥鎖,又是共享鎖,read模式是共享,write是互斥(排它鎖)的。

讀寫鎖有三種狀態:讀加鎖狀態、寫加鎖狀態和不加鎖狀態

讀寫鎖在Java中的具體實現就是ReadWriteLock

一次只有一個線程可以佔有寫模式的讀寫鎖,但是多個線程可以同時佔有讀模式的讀寫鎖。

只有一個線程可以佔有寫狀態的鎖,但可以有多個線程同時佔有讀狀態鎖,這也是它可以實現高併發的原因。當其處於寫狀態鎖下,任何想要嘗試獲得鎖的線程都會被阻塞,直到寫狀態鎖被釋放;如果是處於讀狀態鎖下,允許其它線程獲得它的讀狀態鎖,但是不允許獲得它的寫狀態鎖,直到所有線程的讀狀態鎖被釋放;為了避免想要嘗試寫操作的線程一直得不到寫狀態鎖,當讀寫鎖感知到有線程想要獲得寫狀態鎖時,便會阻塞其後所有想要獲得讀狀態鎖的線程。所以讀寫鎖非常適合資源的讀操作遠多於寫操作的情況。

樂觀鎖/悲觀鎖

"
Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

導讀(需要Java相關學習資料,轉發私信小編“學習”

在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下:

1.公平鎖 / 非公平鎖

2.可重入鎖 / 不可重入鎖

3.獨享鎖 / 共享鎖

4.互斥鎖 / 讀寫鎖

5.樂觀鎖 / 悲觀鎖

6.分段鎖

7.偏向鎖 / 輕量級鎖 / 重量級鎖

8.自旋鎖

上面是很多鎖的名詞,這些分類並不是全是指鎖的狀態,有的指鎖的特性,有的指鎖的設計,下面總結的內容是對每個鎖的名詞進行一定的解釋。

公平鎖/非公平鎖

需要Java相關學習資料,轉發私信小編“java”

公平鎖

公平鎖是指多個線程按照申請鎖的順序來獲取鎖。

非公平鎖

非公平鎖是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。有可能,會造成優先級反轉或者飢餓現象。

對於Java ReentrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在於吞吐量比公平鎖大。

對於Synchronized而言,也是一種非公平鎖。由於其並不像ReentrantLock是通過AQS的來實現線程調度,所以並沒有任何辦法使其變成公平鎖。

可重入鎖/不可重入鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

可重入鎖

廣義上的可重入鎖指的是可重複可遞歸調用的鎖,在外層使用鎖之後,在內層仍然可以使用,並且不發生死鎖(前提得是同一個對象或者class),這樣的鎖就叫做可重入鎖。ReentrantLock和synchronized都是可重入鎖

synchronized void setA() throws Exception{

Thread.sleep(1000);

setB();

}

synchronized void setB() throws Exception{

Thread.sleep(1000);

}

上面的代碼就是一個可重入鎖的一個特點,如果不是可重入鎖的話,setB可能不會被當前線程執行,可能造成死鎖。

不可重入鎖

不可重入鎖,與可重入鎖相反,不可遞歸調用,遞歸調用就發生死鎖。看到一個經典的講解,使用自旋鎖來模擬一個不可重入鎖,代碼如下:

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

public void lock() {

Thread current = Thread.currentThread();

//這句是很經典的“自旋”語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

owner.compareAndSet(current, null);

}

}

代碼也比較簡單,使用原子引用來存放線程,同一線程兩次調用lock()方法,如果不執行unlock()釋放鎖的話,第二次調用自旋的時候就會產生死鎖,這個鎖就不是可重入的,而實際上同一個線程不必每次都去釋放鎖再來獲取鎖,這樣的調度切換是很耗資源的。

把它變成一個可重入鎖

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

private int state = 0;

public void lock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

state++;

return;

}

//這句是很經典的“自旋”式語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

if (state != 0) {

state--;

} else {

owner.compareAndSet(current, null);

}

}

}

}

在執行每次操作之前,判斷當前鎖持有者是否是當前對象,採用state計數,不用每次去釋放鎖。

需要Java相關學習資料,轉發私信小編“java”

ReentrantLock中可重入鎖實現

這裡看非公平鎖的鎖獲取方法:

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;

}

在AQS中維護了一個private volatile int state來計數重入次數,避免了頻繁的持有釋放操作,這樣既提升了效率,又避免了死鎖。

獨享鎖/共享鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

獨享鎖和共享鎖在你去讀C.U.T包下的ReeReentrantLock和ReentrantReadWriteLock你就會發現,它倆一個是獨享一個是共享鎖。

獨享鎖:該鎖每一次只能被一個線程所持有。

共享鎖:該鎖可被多個線程共有,典型的就是ReentrantReadWriteLock裡的讀鎖,它的讀鎖是可以被共享的,但是它的寫鎖確每次只能被獨佔。

另外讀鎖的共享可保證併發讀是非常高效的,但是讀寫和寫寫,寫讀都是互斥的。

獨享鎖與共享鎖也是通過AQS來實現的,通過實現不同的方法,來實現獨享或者共享。

對於Synchronized而言,當然是獨享鎖。

互斥鎖/讀寫鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

互斥鎖

在訪問共享資源之前對進行加鎖操作,在訪問完成之後進行解鎖操作。 加鎖後,任何其他試圖再次加鎖的線程會被阻塞,直到當前進程解鎖。

如果解鎖時有一個以上的線程阻塞,那麼所有該鎖上的線程都被編程就緒狀態, 第一個變為就緒狀態的線程又執行加鎖操作,那麼其他的線程又會進入等待。 在這種方式下,只有一個線程能夠訪問被互斥鎖保護的資源

讀寫鎖

讀寫鎖既是互斥鎖,又是共享鎖,read模式是共享,write是互斥(排它鎖)的。

讀寫鎖有三種狀態:讀加鎖狀態、寫加鎖狀態和不加鎖狀態

讀寫鎖在Java中的具體實現就是ReadWriteLock

一次只有一個線程可以佔有寫模式的讀寫鎖,但是多個線程可以同時佔有讀模式的讀寫鎖。

只有一個線程可以佔有寫狀態的鎖,但可以有多個線程同時佔有讀狀態鎖,這也是它可以實現高併發的原因。當其處於寫狀態鎖下,任何想要嘗試獲得鎖的線程都會被阻塞,直到寫狀態鎖被釋放;如果是處於讀狀態鎖下,允許其它線程獲得它的讀狀態鎖,但是不允許獲得它的寫狀態鎖,直到所有線程的讀狀態鎖被釋放;為了避免想要嘗試寫操作的線程一直得不到寫狀態鎖,當讀寫鎖感知到有線程想要獲得寫狀態鎖時,便會阻塞其後所有想要獲得讀狀態鎖的線程。所以讀寫鎖非常適合資源的讀操作遠多於寫操作的情況。

樂觀鎖/悲觀鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

悲觀鎖

總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完後再把資源轉讓給其它線程)。傳統的關係型數據庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨佔鎖就是悲觀鎖思想的實現。

樂觀鎖

總是假設最好的情況,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號機制和CAS算法實現。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,像數據庫提供的類似於write_condition機制,其實都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的

分段鎖

分段鎖其實是一種鎖的設計,並不是具體的一種鎖,對於ConcurrentHashMap而言,其併發的實現就是通過分段鎖的形式來實現高效的併發操作。

併發容器類的加鎖機制是基於粒度更小的分段鎖,分段鎖也是提升多併發程序性能的重要手段之一。

在併發程序中,串行操作是會降低可伸縮性,並且上下文切換也會減低性能。在鎖上發生競爭時將通水導致這兩種問題,使用獨佔鎖時保護受限資源的時候,基本上是採用串行方式—-每次只能有一個線程能訪問它。所以對於可伸縮性來說最大的威脅就是獨佔鎖。

我們一般有三種方式降低鎖的競爭程度

1、減少鎖的持有時間

2、降低鎖的請求頻率

3、使用帶有協調機制的獨佔鎖,這些機制允許更高的併發性。

在某些情況下我們可以將鎖分解技術進一步擴展為一組獨立對象上的鎖進行分解,這成為分段鎖。

其實說的簡單一點就是

容器裡有多把鎖,每一把鎖用於鎖容器其中一部分數據,那麼當多線程訪問容器裡不同數據段的數據時,線程間就不會存在鎖競爭,從而可以有效的提高併發訪問效率,這就是ConcurrentHashMap所使用的鎖分段技術,首先將數據分成一段一段的存儲,然後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。

比如:在ConcurrentHashMap中使用了一個包含16個鎖的數組,每個鎖保護所有散列桶的1/16,其中第N個散列桶由第(N mod 16)個鎖來保護。假設使用合理的散列算法使關鍵字能夠均勻的分部,那麼這大約能使對鎖的請求減少到越來的1/16。也正是這項技術使得ConcurrentHashMap支持多達16個併發的寫入線程。

偏向鎖/輕量級鎖/重要級鎖

"
Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

導讀(需要Java相關學習資料,轉發私信小編“學習”

在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下:

1.公平鎖 / 非公平鎖

2.可重入鎖 / 不可重入鎖

3.獨享鎖 / 共享鎖

4.互斥鎖 / 讀寫鎖

5.樂觀鎖 / 悲觀鎖

6.分段鎖

7.偏向鎖 / 輕量級鎖 / 重量級鎖

8.自旋鎖

上面是很多鎖的名詞,這些分類並不是全是指鎖的狀態,有的指鎖的特性,有的指鎖的設計,下面總結的內容是對每個鎖的名詞進行一定的解釋。

公平鎖/非公平鎖

需要Java相關學習資料,轉發私信小編“java”

公平鎖

公平鎖是指多個線程按照申請鎖的順序來獲取鎖。

非公平鎖

非公平鎖是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。有可能,會造成優先級反轉或者飢餓現象。

對於Java ReentrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在於吞吐量比公平鎖大。

對於Synchronized而言,也是一種非公平鎖。由於其並不像ReentrantLock是通過AQS的來實現線程調度,所以並沒有任何辦法使其變成公平鎖。

可重入鎖/不可重入鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

可重入鎖

廣義上的可重入鎖指的是可重複可遞歸調用的鎖,在外層使用鎖之後,在內層仍然可以使用,並且不發生死鎖(前提得是同一個對象或者class),這樣的鎖就叫做可重入鎖。ReentrantLock和synchronized都是可重入鎖

synchronized void setA() throws Exception{

Thread.sleep(1000);

setB();

}

synchronized void setB() throws Exception{

Thread.sleep(1000);

}

上面的代碼就是一個可重入鎖的一個特點,如果不是可重入鎖的話,setB可能不會被當前線程執行,可能造成死鎖。

不可重入鎖

不可重入鎖,與可重入鎖相反,不可遞歸調用,遞歸調用就發生死鎖。看到一個經典的講解,使用自旋鎖來模擬一個不可重入鎖,代碼如下:

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

public void lock() {

Thread current = Thread.currentThread();

//這句是很經典的“自旋”語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

owner.compareAndSet(current, null);

}

}

代碼也比較簡單,使用原子引用來存放線程,同一線程兩次調用lock()方法,如果不執行unlock()釋放鎖的話,第二次調用自旋的時候就會產生死鎖,這個鎖就不是可重入的,而實際上同一個線程不必每次都去釋放鎖再來獲取鎖,這樣的調度切換是很耗資源的。

把它變成一個可重入鎖

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

private int state = 0;

public void lock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

state++;

return;

}

//這句是很經典的“自旋”式語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

if (state != 0) {

state--;

} else {

owner.compareAndSet(current, null);

}

}

}

}

在執行每次操作之前,判斷當前鎖持有者是否是當前對象,採用state計數,不用每次去釋放鎖。

需要Java相關學習資料,轉發私信小編“java”

ReentrantLock中可重入鎖實現

這裡看非公平鎖的鎖獲取方法:

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;

}

在AQS中維護了一個private volatile int state來計數重入次數,避免了頻繁的持有釋放操作,這樣既提升了效率,又避免了死鎖。

獨享鎖/共享鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

獨享鎖和共享鎖在你去讀C.U.T包下的ReeReentrantLock和ReentrantReadWriteLock你就會發現,它倆一個是獨享一個是共享鎖。

獨享鎖:該鎖每一次只能被一個線程所持有。

共享鎖:該鎖可被多個線程共有,典型的就是ReentrantReadWriteLock裡的讀鎖,它的讀鎖是可以被共享的,但是它的寫鎖確每次只能被獨佔。

另外讀鎖的共享可保證併發讀是非常高效的,但是讀寫和寫寫,寫讀都是互斥的。

獨享鎖與共享鎖也是通過AQS來實現的,通過實現不同的方法,來實現獨享或者共享。

對於Synchronized而言,當然是獨享鎖。

互斥鎖/讀寫鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

互斥鎖

在訪問共享資源之前對進行加鎖操作,在訪問完成之後進行解鎖操作。 加鎖後,任何其他試圖再次加鎖的線程會被阻塞,直到當前進程解鎖。

如果解鎖時有一個以上的線程阻塞,那麼所有該鎖上的線程都被編程就緒狀態, 第一個變為就緒狀態的線程又執行加鎖操作,那麼其他的線程又會進入等待。 在這種方式下,只有一個線程能夠訪問被互斥鎖保護的資源

讀寫鎖

讀寫鎖既是互斥鎖,又是共享鎖,read模式是共享,write是互斥(排它鎖)的。

讀寫鎖有三種狀態:讀加鎖狀態、寫加鎖狀態和不加鎖狀態

讀寫鎖在Java中的具體實現就是ReadWriteLock

一次只有一個線程可以佔有寫模式的讀寫鎖,但是多個線程可以同時佔有讀模式的讀寫鎖。

只有一個線程可以佔有寫狀態的鎖,但可以有多個線程同時佔有讀狀態鎖,這也是它可以實現高併發的原因。當其處於寫狀態鎖下,任何想要嘗試獲得鎖的線程都會被阻塞,直到寫狀態鎖被釋放;如果是處於讀狀態鎖下,允許其它線程獲得它的讀狀態鎖,但是不允許獲得它的寫狀態鎖,直到所有線程的讀狀態鎖被釋放;為了避免想要嘗試寫操作的線程一直得不到寫狀態鎖,當讀寫鎖感知到有線程想要獲得寫狀態鎖時,便會阻塞其後所有想要獲得讀狀態鎖的線程。所以讀寫鎖非常適合資源的讀操作遠多於寫操作的情況。

樂觀鎖/悲觀鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

悲觀鎖

總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完後再把資源轉讓給其它線程)。傳統的關係型數據庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨佔鎖就是悲觀鎖思想的實現。

樂觀鎖

總是假設最好的情況,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號機制和CAS算法實現。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,像數據庫提供的類似於write_condition機制,其實都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的

分段鎖

分段鎖其實是一種鎖的設計,並不是具體的一種鎖,對於ConcurrentHashMap而言,其併發的實現就是通過分段鎖的形式來實現高效的併發操作。

併發容器類的加鎖機制是基於粒度更小的分段鎖,分段鎖也是提升多併發程序性能的重要手段之一。

在併發程序中,串行操作是會降低可伸縮性,並且上下文切換也會減低性能。在鎖上發生競爭時將通水導致這兩種問題,使用獨佔鎖時保護受限資源的時候,基本上是採用串行方式—-每次只能有一個線程能訪問它。所以對於可伸縮性來說最大的威脅就是獨佔鎖。

我們一般有三種方式降低鎖的競爭程度

1、減少鎖的持有時間

2、降低鎖的請求頻率

3、使用帶有協調機制的獨佔鎖,這些機制允許更高的併發性。

在某些情況下我們可以將鎖分解技術進一步擴展為一組獨立對象上的鎖進行分解,這成為分段鎖。

其實說的簡單一點就是

容器裡有多把鎖,每一把鎖用於鎖容器其中一部分數據,那麼當多線程訪問容器裡不同數據段的數據時,線程間就不會存在鎖競爭,從而可以有效的提高併發訪問效率,這就是ConcurrentHashMap所使用的鎖分段技術,首先將數據分成一段一段的存儲,然後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。

比如:在ConcurrentHashMap中使用了一個包含16個鎖的數組,每個鎖保護所有散列桶的1/16,其中第N個散列桶由第(N mod 16)個鎖來保護。假設使用合理的散列算法使關鍵字能夠均勻的分部,那麼這大約能使對鎖的請求減少到越來的1/16。也正是這項技術使得ConcurrentHashMap支持多達16個併發的寫入線程。

偏向鎖/輕量級鎖/重要級鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

鎖的狀態

1.無鎖狀態

2.偏向鎖狀態

3.輕量級鎖狀態

4.重量級鎖狀態

鎖的狀態是通過對象監視器在對象頭中的字段來表明的。

四種狀態會隨著競爭的情況逐漸升級,而且是不可逆的過程,即不可降級。

這四種狀態都不是Java語言中的鎖,而是Jvm為了提高鎖的獲取與釋放效率而做的優化(使用synchronized時)。

偏向鎖

偏向鎖是指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖。降低獲取鎖的代價。

輕量級

輕量級鎖是指當鎖是偏向鎖的時候,被另一個線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,提高性能。

重量級鎖

重量級鎖是指當鎖為輕量級鎖的時候,另一個線程雖然是自旋,但自旋不會一直持續下去,當自旋一定次數的時候,還沒有獲取到鎖,就會進入阻塞,該鎖膨脹為重量級鎖。重量級鎖會讓其他申請的線程進入阻塞,性能降低。

自旋鎖

"
Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

導讀(需要Java相關學習資料,轉發私信小編“學習”

在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下:

1.公平鎖 / 非公平鎖

2.可重入鎖 / 不可重入鎖

3.獨享鎖 / 共享鎖

4.互斥鎖 / 讀寫鎖

5.樂觀鎖 / 悲觀鎖

6.分段鎖

7.偏向鎖 / 輕量級鎖 / 重量級鎖

8.自旋鎖

上面是很多鎖的名詞,這些分類並不是全是指鎖的狀態,有的指鎖的特性,有的指鎖的設計,下面總結的內容是對每個鎖的名詞進行一定的解釋。

公平鎖/非公平鎖

需要Java相關學習資料,轉發私信小編“java”

公平鎖

公平鎖是指多個線程按照申請鎖的順序來獲取鎖。

非公平鎖

非公平鎖是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。有可能,會造成優先級反轉或者飢餓現象。

對於Java ReentrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在於吞吐量比公平鎖大。

對於Synchronized而言,也是一種非公平鎖。由於其並不像ReentrantLock是通過AQS的來實現線程調度,所以並沒有任何辦法使其變成公平鎖。

可重入鎖/不可重入鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

可重入鎖

廣義上的可重入鎖指的是可重複可遞歸調用的鎖,在外層使用鎖之後,在內層仍然可以使用,並且不發生死鎖(前提得是同一個對象或者class),這樣的鎖就叫做可重入鎖。ReentrantLock和synchronized都是可重入鎖

synchronized void setA() throws Exception{

Thread.sleep(1000);

setB();

}

synchronized void setB() throws Exception{

Thread.sleep(1000);

}

上面的代碼就是一個可重入鎖的一個特點,如果不是可重入鎖的話,setB可能不會被當前線程執行,可能造成死鎖。

不可重入鎖

不可重入鎖,與可重入鎖相反,不可遞歸調用,遞歸調用就發生死鎖。看到一個經典的講解,使用自旋鎖來模擬一個不可重入鎖,代碼如下:

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

public void lock() {

Thread current = Thread.currentThread();

//這句是很經典的“自旋”語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

owner.compareAndSet(current, null);

}

}

代碼也比較簡單,使用原子引用來存放線程,同一線程兩次調用lock()方法,如果不執行unlock()釋放鎖的話,第二次調用自旋的時候就會產生死鎖,這個鎖就不是可重入的,而實際上同一個線程不必每次都去釋放鎖再來獲取鎖,這樣的調度切換是很耗資源的。

把它變成一個可重入鎖

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

private int state = 0;

public void lock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

state++;

return;

}

//這句是很經典的“自旋”式語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

if (state != 0) {

state--;

} else {

owner.compareAndSet(current, null);

}

}

}

}

在執行每次操作之前,判斷當前鎖持有者是否是當前對象,採用state計數,不用每次去釋放鎖。

需要Java相關學習資料,轉發私信小編“java”

ReentrantLock中可重入鎖實現

這裡看非公平鎖的鎖獲取方法:

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;

}

在AQS中維護了一個private volatile int state來計數重入次數,避免了頻繁的持有釋放操作,這樣既提升了效率,又避免了死鎖。

獨享鎖/共享鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

獨享鎖和共享鎖在你去讀C.U.T包下的ReeReentrantLock和ReentrantReadWriteLock你就會發現,它倆一個是獨享一個是共享鎖。

獨享鎖:該鎖每一次只能被一個線程所持有。

共享鎖:該鎖可被多個線程共有,典型的就是ReentrantReadWriteLock裡的讀鎖,它的讀鎖是可以被共享的,但是它的寫鎖確每次只能被獨佔。

另外讀鎖的共享可保證併發讀是非常高效的,但是讀寫和寫寫,寫讀都是互斥的。

獨享鎖與共享鎖也是通過AQS來實現的,通過實現不同的方法,來實現獨享或者共享。

對於Synchronized而言,當然是獨享鎖。

互斥鎖/讀寫鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

互斥鎖

在訪問共享資源之前對進行加鎖操作,在訪問完成之後進行解鎖操作。 加鎖後,任何其他試圖再次加鎖的線程會被阻塞,直到當前進程解鎖。

如果解鎖時有一個以上的線程阻塞,那麼所有該鎖上的線程都被編程就緒狀態, 第一個變為就緒狀態的線程又執行加鎖操作,那麼其他的線程又會進入等待。 在這種方式下,只有一個線程能夠訪問被互斥鎖保護的資源

讀寫鎖

讀寫鎖既是互斥鎖,又是共享鎖,read模式是共享,write是互斥(排它鎖)的。

讀寫鎖有三種狀態:讀加鎖狀態、寫加鎖狀態和不加鎖狀態

讀寫鎖在Java中的具體實現就是ReadWriteLock

一次只有一個線程可以佔有寫模式的讀寫鎖,但是多個線程可以同時佔有讀模式的讀寫鎖。

只有一個線程可以佔有寫狀態的鎖,但可以有多個線程同時佔有讀狀態鎖,這也是它可以實現高併發的原因。當其處於寫狀態鎖下,任何想要嘗試獲得鎖的線程都會被阻塞,直到寫狀態鎖被釋放;如果是處於讀狀態鎖下,允許其它線程獲得它的讀狀態鎖,但是不允許獲得它的寫狀態鎖,直到所有線程的讀狀態鎖被釋放;為了避免想要嘗試寫操作的線程一直得不到寫狀態鎖,當讀寫鎖感知到有線程想要獲得寫狀態鎖時,便會阻塞其後所有想要獲得讀狀態鎖的線程。所以讀寫鎖非常適合資源的讀操作遠多於寫操作的情況。

樂觀鎖/悲觀鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

悲觀鎖

總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完後再把資源轉讓給其它線程)。傳統的關係型數據庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨佔鎖就是悲觀鎖思想的實現。

樂觀鎖

總是假設最好的情況,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號機制和CAS算法實現。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,像數據庫提供的類似於write_condition機制,其實都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的

分段鎖

分段鎖其實是一種鎖的設計,並不是具體的一種鎖,對於ConcurrentHashMap而言,其併發的實現就是通過分段鎖的形式來實現高效的併發操作。

併發容器類的加鎖機制是基於粒度更小的分段鎖,分段鎖也是提升多併發程序性能的重要手段之一。

在併發程序中,串行操作是會降低可伸縮性,並且上下文切換也會減低性能。在鎖上發生競爭時將通水導致這兩種問題,使用獨佔鎖時保護受限資源的時候,基本上是採用串行方式—-每次只能有一個線程能訪問它。所以對於可伸縮性來說最大的威脅就是獨佔鎖。

我們一般有三種方式降低鎖的競爭程度

1、減少鎖的持有時間

2、降低鎖的請求頻率

3、使用帶有協調機制的獨佔鎖,這些機制允許更高的併發性。

在某些情況下我們可以將鎖分解技術進一步擴展為一組獨立對象上的鎖進行分解,這成為分段鎖。

其實說的簡單一點就是

容器裡有多把鎖,每一把鎖用於鎖容器其中一部分數據,那麼當多線程訪問容器裡不同數據段的數據時,線程間就不會存在鎖競爭,從而可以有效的提高併發訪問效率,這就是ConcurrentHashMap所使用的鎖分段技術,首先將數據分成一段一段的存儲,然後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。

比如:在ConcurrentHashMap中使用了一個包含16個鎖的數組,每個鎖保護所有散列桶的1/16,其中第N個散列桶由第(N mod 16)個鎖來保護。假設使用合理的散列算法使關鍵字能夠均勻的分部,那麼這大約能使對鎖的請求減少到越來的1/16。也正是這項技術使得ConcurrentHashMap支持多達16個併發的寫入線程。

偏向鎖/輕量級鎖/重要級鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

鎖的狀態

1.無鎖狀態

2.偏向鎖狀態

3.輕量級鎖狀態

4.重量級鎖狀態

鎖的狀態是通過對象監視器在對象頭中的字段來表明的。

四種狀態會隨著競爭的情況逐漸升級,而且是不可逆的過程,即不可降級。

這四種狀態都不是Java語言中的鎖,而是Jvm為了提高鎖的獲取與釋放效率而做的優化(使用synchronized時)。

偏向鎖

偏向鎖是指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖。降低獲取鎖的代價。

輕量級

輕量級鎖是指當鎖是偏向鎖的時候,被另一個線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,提高性能。

重量級鎖

重量級鎖是指當鎖為輕量級鎖的時候,另一個線程雖然是自旋,但自旋不會一直持續下去,當自旋一定次數的時候,還沒有獲取到鎖,就會進入阻塞,該鎖膨脹為重量級鎖。重量級鎖會讓其他申請的線程進入阻塞,性能降低。

自旋鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

我們知道CAS算法是樂觀鎖的一種實現方式,CAS算法中又涉及到自旋鎖,所以這裡給大家講一下什麼是自旋鎖。

簡單回顧一下CAS算法

CAS是英文單詞Compare and Swap(比較並交換),是一種有名的無鎖算法。無鎖編程,即不使用鎖的情況下實現多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實現變量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三個操作數

1.需要讀寫的內存值 V

2.進行比較的值 A

3.擬寫入的新值 B

更新一個變量的時候,只有當變量的預期值A和內存地址V當中的實際值相同時,才會將內存地址V對應的值修改為B,否則不會執行任何操作。一般情況下是一個自旋操作,即不斷的重試。

什麼是自旋鎖?

自旋鎖(spinlock):是指當一個線程在獲取鎖的時候,如果鎖已經被其它線程獲取,那麼該線程將循環等待,然後不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖才會退出循環

它是為實現保護共享資源而提出一種鎖機制。其實,自旋鎖與互斥鎖比較類似,它們都是為了解決對某項資源的互斥使用。無論是互斥鎖,還是自旋鎖,在任何時刻,最多隻能有一個保持者,也就說,在任何時刻最多隻能有一個執行單元獲得鎖。但是兩者在調度機制上略有不同。對於互斥鎖,如果資源已經被佔用,資源申請者只能進入睡眠狀態。但是自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,調用者就一直循環在那裡看是否該自旋鎖的保持者已經釋放了鎖,”自旋”一詞就是因此而得名。

Java如何實現自旋鎖?

下面是個簡單的例子:

public class SpinLock {

private AtomicReference<Thread> cas = new AtomicReference<Thread>();

public void lock() {

Thread current = Thread.currentThread();

// 利用CAS

while (!cas.compareAndSet(null, current)) {

// DO nothing

}

}

public void unlock() {

Thread current = Thread.currentThread();

cas.compareAndSet(current, null);

}

}

lock()方法利用的CAS,當第一個線程A獲取鎖的時候,能夠成功獲取到,不會進入while循環,如果此時線程A沒有釋放鎖,另一個線程B又來獲取鎖,此時由於不滿足CAS,所以就會進入while循環,不斷判斷是否滿足CAS,直到A線程調用unlock方法釋放了該鎖。

自旋鎖存在的問題

1、如果某個線程持有鎖的時間過長,就會導致其它等待獲取鎖的線程進入循環等待,消耗CPU。使用不當會造成CPU使用率極高。

2、上面Java實現的自旋鎖不是公平的,即無法滿足等待時間最長的線程優先獲取鎖。不公平的鎖就會存在“線程飢餓”問題。

自旋鎖的優點

1、自旋鎖不會使線程狀態發生切換,一直處於用戶態,即線程一直都是active的;不會使線程進入阻塞狀態,減少了不必要的上下文切換,執行速度快

2、非自旋鎖在獲取不到鎖的時候會進入阻塞狀態,從而進入內核態,當獲取到鎖的時候需要從內核態恢復,需要線程上下文切換。 (線程被阻塞後便進入內核(Linux)調度狀態,這個會導致系統在用戶態與內核態之間來回切換,嚴重影響鎖的性能)

可重入的自旋鎖和不可重入的自旋鎖

文章開始的時候的那段代碼,仔細分析一下就可以看出,它是不支持重入的,即當一個線程第一次已經獲取到了該鎖,在鎖釋放之前又一次重新獲取該鎖,第二次就不能成功獲取到。由於不滿足CAS,所以第二次獲取會進入while循環等待,而如果是可重入鎖,第二次也是應該能夠成功獲取到的。

而且,即使第二次能夠成功獲取,那麼當第一次釋放鎖的時候,第二次獲取到的鎖也會被釋放,而這是不合理的。

為了實現可重入鎖,我們需要引入一個計數器,用來記錄獲取鎖的線程數。

public class ReentrantSpinLock {

private AtomicReference<Thread> cas = new AtomicReference<Thread>();

private int count;

public void lock() {

Thread current = Thread.currentThread();

if (current == cas.get()) { // 如果當前線程已經獲取到了鎖,線程數增加一,然後返回

count++;

return;

}

// 如果沒獲取到鎖,則通過CAS自旋

while (!cas.compareAndSet(null, current)) {

// DO nothing

}

}

public void unlock() {

Thread cur = Thread.currentThread();

if (cur == cas.get()) {

if (count > 0) {// 如果大於0,表示當前線程多次獲取了該鎖,釋放鎖通過count減一來模擬

count--;

} else {// 如果count==0,可以將鎖釋放,這樣就能保證獲取鎖的次數與釋放鎖的次數是一致的了。

cas.compareAndSet(cur, null);

}

}

}

}

自旋鎖與互斥鎖

1.自旋鎖與互斥鎖都是為了實現保護資源共享的機制。

2.無論是自旋鎖還是互斥鎖,在任意時刻,都最多隻能有一個保持者。

3獲取互斥鎖的線程,如果鎖已經被佔用,則該線程將進入睡眠狀態;獲取自旋鎖的線程則不會睡眠,而是一直循環等待鎖釋放。

自旋鎖總結

1.自旋鎖:線程獲取鎖的時候,如果鎖被其他線程持有,則當前線程將循環等待,直到獲取到鎖。

2.自旋鎖等待期間,線程的狀態不會改變,線程一直是用戶態並且是活動的(active)。

3.自旋鎖如果持有鎖的時間太長,則會導致其它等待獲取鎖的線程耗盡CPU。

4.自旋鎖本身無法保證公平性,同時也無法保證可重入性。

5.基於自旋鎖,可以實現具備公平性和可重入性質的鎖。

需要Java相關學習資料,轉發私信小編“學習”

"
Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

導讀(需要Java相關學習資料,轉發私信小編“學習”

在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下:

1.公平鎖 / 非公平鎖

2.可重入鎖 / 不可重入鎖

3.獨享鎖 / 共享鎖

4.互斥鎖 / 讀寫鎖

5.樂觀鎖 / 悲觀鎖

6.分段鎖

7.偏向鎖 / 輕量級鎖 / 重量級鎖

8.自旋鎖

上面是很多鎖的名詞,這些分類並不是全是指鎖的狀態,有的指鎖的特性,有的指鎖的設計,下面總結的內容是對每個鎖的名詞進行一定的解釋。

公平鎖/非公平鎖

需要Java相關學習資料,轉發私信小編“java”

公平鎖

公平鎖是指多個線程按照申請鎖的順序來獲取鎖。

非公平鎖

非公平鎖是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。有可能,會造成優先級反轉或者飢餓現象。

對於Java ReentrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在於吞吐量比公平鎖大。

對於Synchronized而言,也是一種非公平鎖。由於其並不像ReentrantLock是通過AQS的來實現線程調度,所以並沒有任何辦法使其變成公平鎖。

可重入鎖/不可重入鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

可重入鎖

廣義上的可重入鎖指的是可重複可遞歸調用的鎖,在外層使用鎖之後,在內層仍然可以使用,並且不發生死鎖(前提得是同一個對象或者class),這樣的鎖就叫做可重入鎖。ReentrantLock和synchronized都是可重入鎖

synchronized void setA() throws Exception{

Thread.sleep(1000);

setB();

}

synchronized void setB() throws Exception{

Thread.sleep(1000);

}

上面的代碼就是一個可重入鎖的一個特點,如果不是可重入鎖的話,setB可能不會被當前線程執行,可能造成死鎖。

不可重入鎖

不可重入鎖,與可重入鎖相反,不可遞歸調用,遞歸調用就發生死鎖。看到一個經典的講解,使用自旋鎖來模擬一個不可重入鎖,代碼如下:

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

public void lock() {

Thread current = Thread.currentThread();

//這句是很經典的“自旋”語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

owner.compareAndSet(current, null);

}

}

代碼也比較簡單,使用原子引用來存放線程,同一線程兩次調用lock()方法,如果不執行unlock()釋放鎖的話,第二次調用自旋的時候就會產生死鎖,這個鎖就不是可重入的,而實際上同一個線程不必每次都去釋放鎖再來獲取鎖,這樣的調度切換是很耗資源的。

把它變成一個可重入鎖

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

private int state = 0;

public void lock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

state++;

return;

}

//這句是很經典的“自旋”式語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

if (state != 0) {

state--;

} else {

owner.compareAndSet(current, null);

}

}

}

}

在執行每次操作之前,判斷當前鎖持有者是否是當前對象,採用state計數,不用每次去釋放鎖。

需要Java相關學習資料,轉發私信小編“java”

ReentrantLock中可重入鎖實現

這裡看非公平鎖的鎖獲取方法:

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;

}

在AQS中維護了一個private volatile int state來計數重入次數,避免了頻繁的持有釋放操作,這樣既提升了效率,又避免了死鎖。

獨享鎖/共享鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

獨享鎖和共享鎖在你去讀C.U.T包下的ReeReentrantLock和ReentrantReadWriteLock你就會發現,它倆一個是獨享一個是共享鎖。

獨享鎖:該鎖每一次只能被一個線程所持有。

共享鎖:該鎖可被多個線程共有,典型的就是ReentrantReadWriteLock裡的讀鎖,它的讀鎖是可以被共享的,但是它的寫鎖確每次只能被獨佔。

另外讀鎖的共享可保證併發讀是非常高效的,但是讀寫和寫寫,寫讀都是互斥的。

獨享鎖與共享鎖也是通過AQS來實現的,通過實現不同的方法,來實現獨享或者共享。

對於Synchronized而言,當然是獨享鎖。

互斥鎖/讀寫鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

互斥鎖

在訪問共享資源之前對進行加鎖操作,在訪問完成之後進行解鎖操作。 加鎖後,任何其他試圖再次加鎖的線程會被阻塞,直到當前進程解鎖。

如果解鎖時有一個以上的線程阻塞,那麼所有該鎖上的線程都被編程就緒狀態, 第一個變為就緒狀態的線程又執行加鎖操作,那麼其他的線程又會進入等待。 在這種方式下,只有一個線程能夠訪問被互斥鎖保護的資源

讀寫鎖

讀寫鎖既是互斥鎖,又是共享鎖,read模式是共享,write是互斥(排它鎖)的。

讀寫鎖有三種狀態:讀加鎖狀態、寫加鎖狀態和不加鎖狀態

讀寫鎖在Java中的具體實現就是ReadWriteLock

一次只有一個線程可以佔有寫模式的讀寫鎖,但是多個線程可以同時佔有讀模式的讀寫鎖。

只有一個線程可以佔有寫狀態的鎖,但可以有多個線程同時佔有讀狀態鎖,這也是它可以實現高併發的原因。當其處於寫狀態鎖下,任何想要嘗試獲得鎖的線程都會被阻塞,直到寫狀態鎖被釋放;如果是處於讀狀態鎖下,允許其它線程獲得它的讀狀態鎖,但是不允許獲得它的寫狀態鎖,直到所有線程的讀狀態鎖被釋放;為了避免想要嘗試寫操作的線程一直得不到寫狀態鎖,當讀寫鎖感知到有線程想要獲得寫狀態鎖時,便會阻塞其後所有想要獲得讀狀態鎖的線程。所以讀寫鎖非常適合資源的讀操作遠多於寫操作的情況。

樂觀鎖/悲觀鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

悲觀鎖

總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完後再把資源轉讓給其它線程)。傳統的關係型數據庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨佔鎖就是悲觀鎖思想的實現。

樂觀鎖

總是假設最好的情況,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號機制和CAS算法實現。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,像數據庫提供的類似於write_condition機制,其實都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的

分段鎖

分段鎖其實是一種鎖的設計,並不是具體的一種鎖,對於ConcurrentHashMap而言,其併發的實現就是通過分段鎖的形式來實現高效的併發操作。

併發容器類的加鎖機制是基於粒度更小的分段鎖,分段鎖也是提升多併發程序性能的重要手段之一。

在併發程序中,串行操作是會降低可伸縮性,並且上下文切換也會減低性能。在鎖上發生競爭時將通水導致這兩種問題,使用獨佔鎖時保護受限資源的時候,基本上是採用串行方式—-每次只能有一個線程能訪問它。所以對於可伸縮性來說最大的威脅就是獨佔鎖。

我們一般有三種方式降低鎖的競爭程度

1、減少鎖的持有時間

2、降低鎖的請求頻率

3、使用帶有協調機制的獨佔鎖,這些機制允許更高的併發性。

在某些情況下我們可以將鎖分解技術進一步擴展為一組獨立對象上的鎖進行分解,這成為分段鎖。

其實說的簡單一點就是

容器裡有多把鎖,每一把鎖用於鎖容器其中一部分數據,那麼當多線程訪問容器裡不同數據段的數據時,線程間就不會存在鎖競爭,從而可以有效的提高併發訪問效率,這就是ConcurrentHashMap所使用的鎖分段技術,首先將數據分成一段一段的存儲,然後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。

比如:在ConcurrentHashMap中使用了一個包含16個鎖的數組,每個鎖保護所有散列桶的1/16,其中第N個散列桶由第(N mod 16)個鎖來保護。假設使用合理的散列算法使關鍵字能夠均勻的分部,那麼這大約能使對鎖的請求減少到越來的1/16。也正是這項技術使得ConcurrentHashMap支持多達16個併發的寫入線程。

偏向鎖/輕量級鎖/重要級鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

鎖的狀態

1.無鎖狀態

2.偏向鎖狀態

3.輕量級鎖狀態

4.重量級鎖狀態

鎖的狀態是通過對象監視器在對象頭中的字段來表明的。

四種狀態會隨著競爭的情況逐漸升級,而且是不可逆的過程,即不可降級。

這四種狀態都不是Java語言中的鎖,而是Jvm為了提高鎖的獲取與釋放效率而做的優化(使用synchronized時)。

偏向鎖

偏向鎖是指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖。降低獲取鎖的代價。

輕量級

輕量級鎖是指當鎖是偏向鎖的時候,被另一個線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,提高性能。

重量級鎖

重量級鎖是指當鎖為輕量級鎖的時候,另一個線程雖然是自旋,但自旋不會一直持續下去,當自旋一定次數的時候,還沒有獲取到鎖,就會進入阻塞,該鎖膨脹為重量級鎖。重量級鎖會讓其他申請的線程進入阻塞,性能降低。

自旋鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

我們知道CAS算法是樂觀鎖的一種實現方式,CAS算法中又涉及到自旋鎖,所以這裡給大家講一下什麼是自旋鎖。

簡單回顧一下CAS算法

CAS是英文單詞Compare and Swap(比較並交換),是一種有名的無鎖算法。無鎖編程,即不使用鎖的情況下實現多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實現變量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三個操作數

1.需要讀寫的內存值 V

2.進行比較的值 A

3.擬寫入的新值 B

更新一個變量的時候,只有當變量的預期值A和內存地址V當中的實際值相同時,才會將內存地址V對應的值修改為B,否則不會執行任何操作。一般情況下是一個自旋操作,即不斷的重試。

什麼是自旋鎖?

自旋鎖(spinlock):是指當一個線程在獲取鎖的時候,如果鎖已經被其它線程獲取,那麼該線程將循環等待,然後不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖才會退出循環

它是為實現保護共享資源而提出一種鎖機制。其實,自旋鎖與互斥鎖比較類似,它們都是為了解決對某項資源的互斥使用。無論是互斥鎖,還是自旋鎖,在任何時刻,最多隻能有一個保持者,也就說,在任何時刻最多隻能有一個執行單元獲得鎖。但是兩者在調度機制上略有不同。對於互斥鎖,如果資源已經被佔用,資源申請者只能進入睡眠狀態。但是自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,調用者就一直循環在那裡看是否該自旋鎖的保持者已經釋放了鎖,”自旋”一詞就是因此而得名。

Java如何實現自旋鎖?

下面是個簡單的例子:

public class SpinLock {

private AtomicReference<Thread> cas = new AtomicReference<Thread>();

public void lock() {

Thread current = Thread.currentThread();

// 利用CAS

while (!cas.compareAndSet(null, current)) {

// DO nothing

}

}

public void unlock() {

Thread current = Thread.currentThread();

cas.compareAndSet(current, null);

}

}

lock()方法利用的CAS,當第一個線程A獲取鎖的時候,能夠成功獲取到,不會進入while循環,如果此時線程A沒有釋放鎖,另一個線程B又來獲取鎖,此時由於不滿足CAS,所以就會進入while循環,不斷判斷是否滿足CAS,直到A線程調用unlock方法釋放了該鎖。

自旋鎖存在的問題

1、如果某個線程持有鎖的時間過長,就會導致其它等待獲取鎖的線程進入循環等待,消耗CPU。使用不當會造成CPU使用率極高。

2、上面Java實現的自旋鎖不是公平的,即無法滿足等待時間最長的線程優先獲取鎖。不公平的鎖就會存在“線程飢餓”問題。

自旋鎖的優點

1、自旋鎖不會使線程狀態發生切換,一直處於用戶態,即線程一直都是active的;不會使線程進入阻塞狀態,減少了不必要的上下文切換,執行速度快

2、非自旋鎖在獲取不到鎖的時候會進入阻塞狀態,從而進入內核態,當獲取到鎖的時候需要從內核態恢復,需要線程上下文切換。 (線程被阻塞後便進入內核(Linux)調度狀態,這個會導致系統在用戶態與內核態之間來回切換,嚴重影響鎖的性能)

可重入的自旋鎖和不可重入的自旋鎖

文章開始的時候的那段代碼,仔細分析一下就可以看出,它是不支持重入的,即當一個線程第一次已經獲取到了該鎖,在鎖釋放之前又一次重新獲取該鎖,第二次就不能成功獲取到。由於不滿足CAS,所以第二次獲取會進入while循環等待,而如果是可重入鎖,第二次也是應該能夠成功獲取到的。

而且,即使第二次能夠成功獲取,那麼當第一次釋放鎖的時候,第二次獲取到的鎖也會被釋放,而這是不合理的。

為了實現可重入鎖,我們需要引入一個計數器,用來記錄獲取鎖的線程數。

public class ReentrantSpinLock {

private AtomicReference<Thread> cas = new AtomicReference<Thread>();

private int count;

public void lock() {

Thread current = Thread.currentThread();

if (current == cas.get()) { // 如果當前線程已經獲取到了鎖,線程數增加一,然後返回

count++;

return;

}

// 如果沒獲取到鎖,則通過CAS自旋

while (!cas.compareAndSet(null, current)) {

// DO nothing

}

}

public void unlock() {

Thread cur = Thread.currentThread();

if (cur == cas.get()) {

if (count > 0) {// 如果大於0,表示當前線程多次獲取了該鎖,釋放鎖通過count減一來模擬

count--;

} else {// 如果count==0,可以將鎖釋放,這樣就能保證獲取鎖的次數與釋放鎖的次數是一致的了。

cas.compareAndSet(cur, null);

}

}

}

}

自旋鎖與互斥鎖

1.自旋鎖與互斥鎖都是為了實現保護資源共享的機制。

2.無論是自旋鎖還是互斥鎖,在任意時刻,都最多隻能有一個保持者。

3獲取互斥鎖的線程,如果鎖已經被佔用,則該線程將進入睡眠狀態;獲取自旋鎖的線程則不會睡眠,而是一直循環等待鎖釋放。

自旋鎖總結

1.自旋鎖:線程獲取鎖的時候,如果鎖被其他線程持有,則當前線程將循環等待,直到獲取到鎖。

2.自旋鎖等待期間,線程的狀態不會改變,線程一直是用戶態並且是活動的(active)。

3.自旋鎖如果持有鎖的時間太長,則會導致其它等待獲取鎖的線程耗盡CPU。

4.自旋鎖本身無法保證公平性,同時也無法保證可重入性。

5.基於自旋鎖,可以實現具備公平性和可重入性質的鎖。

需要Java相關學習資料,轉發私信小編“學習”

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

"
Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

導讀(需要Java相關學習資料,轉發私信小編“學習”

在讀很多併發文章中,會提及各種各樣鎖如公平鎖,樂觀鎖等等,這篇文章介紹各種鎖的分類。介紹的內容如下:

1.公平鎖 / 非公平鎖

2.可重入鎖 / 不可重入鎖

3.獨享鎖 / 共享鎖

4.互斥鎖 / 讀寫鎖

5.樂觀鎖 / 悲觀鎖

6.分段鎖

7.偏向鎖 / 輕量級鎖 / 重量級鎖

8.自旋鎖

上面是很多鎖的名詞,這些分類並不是全是指鎖的狀態,有的指鎖的特性,有的指鎖的設計,下面總結的內容是對每個鎖的名詞進行一定的解釋。

公平鎖/非公平鎖

需要Java相關學習資料,轉發私信小編“java”

公平鎖

公平鎖是指多個線程按照申請鎖的順序來獲取鎖。

非公平鎖

非公平鎖是指多個線程獲取鎖的順序並不是按照申請鎖的順序,有可能後申請的線程比先申請的線程優先獲取鎖。有可能,會造成優先級反轉或者飢餓現象。

對於Java ReentrantLock而言,通過構造函數指定該鎖是否是公平鎖,默認是非公平鎖。非公平鎖的優點在於吞吐量比公平鎖大。

對於Synchronized而言,也是一種非公平鎖。由於其並不像ReentrantLock是通過AQS的來實現線程調度,所以並沒有任何辦法使其變成公平鎖。

可重入鎖/不可重入鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

可重入鎖

廣義上的可重入鎖指的是可重複可遞歸調用的鎖,在外層使用鎖之後,在內層仍然可以使用,並且不發生死鎖(前提得是同一個對象或者class),這樣的鎖就叫做可重入鎖。ReentrantLock和synchronized都是可重入鎖

synchronized void setA() throws Exception{

Thread.sleep(1000);

setB();

}

synchronized void setB() throws Exception{

Thread.sleep(1000);

}

上面的代碼就是一個可重入鎖的一個特點,如果不是可重入鎖的話,setB可能不會被當前線程執行,可能造成死鎖。

不可重入鎖

不可重入鎖,與可重入鎖相反,不可遞歸調用,遞歸調用就發生死鎖。看到一個經典的講解,使用自旋鎖來模擬一個不可重入鎖,代碼如下:

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

public void lock() {

Thread current = Thread.currentThread();

//這句是很經典的“自旋”語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

owner.compareAndSet(current, null);

}

}

代碼也比較簡單,使用原子引用來存放線程,同一線程兩次調用lock()方法,如果不執行unlock()釋放鎖的話,第二次調用自旋的時候就會產生死鎖,這個鎖就不是可重入的,而實際上同一個線程不必每次都去釋放鎖再來獲取鎖,這樣的調度切換是很耗資源的。

把它變成一個可重入鎖

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

private int state = 0;

public void lock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

state++;

return;

}

//這句是很經典的“自旋”式語法,AtomicInteger中也有

for (;;) {

if (!owner.compareAndSet(null, current)) {

return;

}

}

}

public void unlock() {

Thread current = Thread.currentThread();

if (current == owner.get()) {

if (state != 0) {

state--;

} else {

owner.compareAndSet(current, null);

}

}

}

}

在執行每次操作之前,判斷當前鎖持有者是否是當前對象,採用state計數,不用每次去釋放鎖。

需要Java相關學習資料,轉發私信小編“java”

ReentrantLock中可重入鎖實現

這裡看非公平鎖的鎖獲取方法:

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;

}

在AQS中維護了一個private volatile int state來計數重入次數,避免了頻繁的持有釋放操作,這樣既提升了效率,又避免了死鎖。

獨享鎖/共享鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

獨享鎖和共享鎖在你去讀C.U.T包下的ReeReentrantLock和ReentrantReadWriteLock你就會發現,它倆一個是獨享一個是共享鎖。

獨享鎖:該鎖每一次只能被一個線程所持有。

共享鎖:該鎖可被多個線程共有,典型的就是ReentrantReadWriteLock裡的讀鎖,它的讀鎖是可以被共享的,但是它的寫鎖確每次只能被獨佔。

另外讀鎖的共享可保證併發讀是非常高效的,但是讀寫和寫寫,寫讀都是互斥的。

獨享鎖與共享鎖也是通過AQS來實現的,通過實現不同的方法,來實現獨享或者共享。

對於Synchronized而言,當然是獨享鎖。

互斥鎖/讀寫鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

互斥鎖

在訪問共享資源之前對進行加鎖操作,在訪問完成之後進行解鎖操作。 加鎖後,任何其他試圖再次加鎖的線程會被阻塞,直到當前進程解鎖。

如果解鎖時有一個以上的線程阻塞,那麼所有該鎖上的線程都被編程就緒狀態, 第一個變為就緒狀態的線程又執行加鎖操作,那麼其他的線程又會進入等待。 在這種方式下,只有一個線程能夠訪問被互斥鎖保護的資源

讀寫鎖

讀寫鎖既是互斥鎖,又是共享鎖,read模式是共享,write是互斥(排它鎖)的。

讀寫鎖有三種狀態:讀加鎖狀態、寫加鎖狀態和不加鎖狀態

讀寫鎖在Java中的具體實現就是ReadWriteLock

一次只有一個線程可以佔有寫模式的讀寫鎖,但是多個線程可以同時佔有讀模式的讀寫鎖。

只有一個線程可以佔有寫狀態的鎖,但可以有多個線程同時佔有讀狀態鎖,這也是它可以實現高併發的原因。當其處於寫狀態鎖下,任何想要嘗試獲得鎖的線程都會被阻塞,直到寫狀態鎖被釋放;如果是處於讀狀態鎖下,允許其它線程獲得它的讀狀態鎖,但是不允許獲得它的寫狀態鎖,直到所有線程的讀狀態鎖被釋放;為了避免想要嘗試寫操作的線程一直得不到寫狀態鎖,當讀寫鎖感知到有線程想要獲得寫狀態鎖時,便會阻塞其後所有想要獲得讀狀態鎖的線程。所以讀寫鎖非常適合資源的讀操作遠多於寫操作的情況。

樂觀鎖/悲觀鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

悲觀鎖

總是假設最壞的情況,每次去拿數據的時候都認為別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會阻塞直到它拿到鎖(共享資源每次只給一個線程使用,其它線程阻塞,用完後再把資源轉讓給其它線程)。傳統的關係型數據庫裡邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨佔鎖就是悲觀鎖思想的實現。

樂觀鎖

總是假設最好的情況,每次去拿數據的時候都認為別人不會修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間別人有沒有去更新這個數據,可以使用版本號機制和CAS算法實現。樂觀鎖適用於多讀的應用類型,這樣可以提高吞吐量,像數據庫提供的類似於write_condition機制,其實都是提供的樂觀鎖。在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實現方式CAS實現的

分段鎖

分段鎖其實是一種鎖的設計,並不是具體的一種鎖,對於ConcurrentHashMap而言,其併發的實現就是通過分段鎖的形式來實現高效的併發操作。

併發容器類的加鎖機制是基於粒度更小的分段鎖,分段鎖也是提升多併發程序性能的重要手段之一。

在併發程序中,串行操作是會降低可伸縮性,並且上下文切換也會減低性能。在鎖上發生競爭時將通水導致這兩種問題,使用獨佔鎖時保護受限資源的時候,基本上是採用串行方式—-每次只能有一個線程能訪問它。所以對於可伸縮性來說最大的威脅就是獨佔鎖。

我們一般有三種方式降低鎖的競爭程度

1、減少鎖的持有時間

2、降低鎖的請求頻率

3、使用帶有協調機制的獨佔鎖,這些機制允許更高的併發性。

在某些情況下我們可以將鎖分解技術進一步擴展為一組獨立對象上的鎖進行分解,這成為分段鎖。

其實說的簡單一點就是

容器裡有多把鎖,每一把鎖用於鎖容器其中一部分數據,那麼當多線程訪問容器裡不同數據段的數據時,線程間就不會存在鎖競爭,從而可以有效的提高併發訪問效率,這就是ConcurrentHashMap所使用的鎖分段技術,首先將數據分成一段一段的存儲,然後給每一段數據配一把鎖,當一個線程佔用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。

比如:在ConcurrentHashMap中使用了一個包含16個鎖的數組,每個鎖保護所有散列桶的1/16,其中第N個散列桶由第(N mod 16)個鎖來保護。假設使用合理的散列算法使關鍵字能夠均勻的分部,那麼這大約能使對鎖的請求減少到越來的1/16。也正是這項技術使得ConcurrentHashMap支持多達16個併發的寫入線程。

偏向鎖/輕量級鎖/重要級鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

鎖的狀態

1.無鎖狀態

2.偏向鎖狀態

3.輕量級鎖狀態

4.重量級鎖狀態

鎖的狀態是通過對象監視器在對象頭中的字段來表明的。

四種狀態會隨著競爭的情況逐漸升級,而且是不可逆的過程,即不可降級。

這四種狀態都不是Java語言中的鎖,而是Jvm為了提高鎖的獲取與釋放效率而做的優化(使用synchronized時)。

偏向鎖

偏向鎖是指一段同步代碼一直被一個線程所訪問,那麼該線程會自動獲取鎖。降低獲取鎖的代價。

輕量級

輕量級鎖是指當鎖是偏向鎖的時候,被另一個線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,提高性能。

重量級鎖

重量級鎖是指當鎖為輕量級鎖的時候,另一個線程雖然是自旋,但自旋不會一直持續下去,當自旋一定次數的時候,還沒有獲取到鎖,就會進入阻塞,該鎖膨脹為重量級鎖。重量級鎖會讓其他申請的線程進入阻塞,性能降低。

自旋鎖

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

我們知道CAS算法是樂觀鎖的一種實現方式,CAS算法中又涉及到自旋鎖,所以這裡給大家講一下什麼是自旋鎖。

簡單回顧一下CAS算法

CAS是英文單詞Compare and Swap(比較並交換),是一種有名的無鎖算法。無鎖編程,即不使用鎖的情況下實現多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實現變量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三個操作數

1.需要讀寫的內存值 V

2.進行比較的值 A

3.擬寫入的新值 B

更新一個變量的時候,只有當變量的預期值A和內存地址V當中的實際值相同時,才會將內存地址V對應的值修改為B,否則不會執行任何操作。一般情況下是一個自旋操作,即不斷的重試。

什麼是自旋鎖?

自旋鎖(spinlock):是指當一個線程在獲取鎖的時候,如果鎖已經被其它線程獲取,那麼該線程將循環等待,然後不斷的判斷鎖是否能夠被成功獲取,直到獲取到鎖才會退出循環

它是為實現保護共享資源而提出一種鎖機制。其實,自旋鎖與互斥鎖比較類似,它們都是為了解決對某項資源的互斥使用。無論是互斥鎖,還是自旋鎖,在任何時刻,最多隻能有一個保持者,也就說,在任何時刻最多隻能有一個執行單元獲得鎖。但是兩者在調度機制上略有不同。對於互斥鎖,如果資源已經被佔用,資源申請者只能進入睡眠狀態。但是自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,調用者就一直循環在那裡看是否該自旋鎖的保持者已經釋放了鎖,”自旋”一詞就是因此而得名。

Java如何實現自旋鎖?

下面是個簡單的例子:

public class SpinLock {

private AtomicReference<Thread> cas = new AtomicReference<Thread>();

public void lock() {

Thread current = Thread.currentThread();

// 利用CAS

while (!cas.compareAndSet(null, current)) {

// DO nothing

}

}

public void unlock() {

Thread current = Thread.currentThread();

cas.compareAndSet(current, null);

}

}

lock()方法利用的CAS,當第一個線程A獲取鎖的時候,能夠成功獲取到,不會進入while循環,如果此時線程A沒有釋放鎖,另一個線程B又來獲取鎖,此時由於不滿足CAS,所以就會進入while循環,不斷判斷是否滿足CAS,直到A線程調用unlock方法釋放了該鎖。

自旋鎖存在的問題

1、如果某個線程持有鎖的時間過長,就會導致其它等待獲取鎖的線程進入循環等待,消耗CPU。使用不當會造成CPU使用率極高。

2、上面Java實現的自旋鎖不是公平的,即無法滿足等待時間最長的線程優先獲取鎖。不公平的鎖就會存在“線程飢餓”問題。

自旋鎖的優點

1、自旋鎖不會使線程狀態發生切換,一直處於用戶態,即線程一直都是active的;不會使線程進入阻塞狀態,減少了不必要的上下文切換,執行速度快

2、非自旋鎖在獲取不到鎖的時候會進入阻塞狀態,從而進入內核態,當獲取到鎖的時候需要從內核態恢復,需要線程上下文切換。 (線程被阻塞後便進入內核(Linux)調度狀態,這個會導致系統在用戶態與內核態之間來回切換,嚴重影響鎖的性能)

可重入的自旋鎖和不可重入的自旋鎖

文章開始的時候的那段代碼,仔細分析一下就可以看出,它是不支持重入的,即當一個線程第一次已經獲取到了該鎖,在鎖釋放之前又一次重新獲取該鎖,第二次就不能成功獲取到。由於不滿足CAS,所以第二次獲取會進入while循環等待,而如果是可重入鎖,第二次也是應該能夠成功獲取到的。

而且,即使第二次能夠成功獲取,那麼當第一次釋放鎖的時候,第二次獲取到的鎖也會被釋放,而這是不合理的。

為了實現可重入鎖,我們需要引入一個計數器,用來記錄獲取鎖的線程數。

public class ReentrantSpinLock {

private AtomicReference<Thread> cas = new AtomicReference<Thread>();

private int count;

public void lock() {

Thread current = Thread.currentThread();

if (current == cas.get()) { // 如果當前線程已經獲取到了鎖,線程數增加一,然後返回

count++;

return;

}

// 如果沒獲取到鎖,則通過CAS自旋

while (!cas.compareAndSet(null, current)) {

// DO nothing

}

}

public void unlock() {

Thread cur = Thread.currentThread();

if (cur == cas.get()) {

if (count > 0) {// 如果大於0,表示當前線程多次獲取了該鎖,釋放鎖通過count減一來模擬

count--;

} else {// 如果count==0,可以將鎖釋放,這樣就能保證獲取鎖的次數與釋放鎖的次數是一致的了。

cas.compareAndSet(cur, null);

}

}

}

}

自旋鎖與互斥鎖

1.自旋鎖與互斥鎖都是為了實現保護資源共享的機制。

2.無論是自旋鎖還是互斥鎖,在任意時刻,都最多隻能有一個保持者。

3獲取互斥鎖的線程,如果鎖已經被佔用,則該線程將進入睡眠狀態;獲取自旋鎖的線程則不會睡眠,而是一直循環等待鎖釋放。

自旋鎖總結

1.自旋鎖:線程獲取鎖的時候,如果鎖被其他線程持有,則當前線程將循環等待,直到獲取到鎖。

2.自旋鎖等待期間,線程的狀態不會改變,線程一直是用戶態並且是活動的(active)。

3.自旋鎖如果持有鎖的時間太長,則會導致其它等待獲取鎖的線程耗盡CPU。

4.自旋鎖本身無法保證公平性,同時也無法保證可重入性。

5.基於自旋鎖,可以實現具備公平性和可重入性質的鎖。

需要Java相關學習資料,轉發私信小編“學習”

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

Java中15種鎖的介紹!鎖不鎖不知道,反正Java挺好用的

"

相關推薦

推薦中...