系列文章目錄
第二十八章 分布式鎖框架-Redisson
第二十七章 CAS
第二十六章 Java鎖的分類
第二十五章 Java多線程安全與鎖
第二章 CountDownLatch和Semaphone的應(yīng)用
第一章 Java線程池技術(shù)應(yīng)用
前言
本章節(jié)介紹Java中的幾種常見的鎖:公平鎖和非公平鎖、可重入鎖、獨(dú)享鎖/共享鎖、互斥鎖/讀寫鎖、樂觀鎖/悲觀鎖、分段鎖、偏向鎖/輕量級(jí)鎖/重量級(jí)鎖、自旋鎖。
1、公平鎖和非公平鎖
公平鎖是指多個(gè)線程按照申請鎖的順序來獲取鎖。
非公平鎖是指多個(gè)線程獲取鎖的順序并不是按照申請鎖的順序,有可能后申請的線程比先申請的線程優(yōu)先獲取鎖。有可能,會(huì)造成優(yōu)先級(jí)反轉(zhuǎn)或者饑餓現(xiàn)象。
對于Java ReentrantLock而言,通過構(gòu)造函數(shù)指定該鎖是否是公平鎖,默認(rèn)是非公平鎖的優(yōu)點(diǎn)在于吞吐量比公平鎖大。
對于synchronized而言,也是一種非公平鎖。由于其并不像ReentrantLock是通過AQS的來實(shí)現(xiàn)線程調(diào)度,所以并沒有任何辦法使其變成公平鎖。
package com.xxxx.reids.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/***
* @title ReentrantLockFair
* @desctption 公平鎖
* @author Kelvin
* @create 2023/5/29 16:10
**/
public class ReentrantLockFair {
public static void main(String[] args) {
//true表示公平鎖
Lock lock = new ReentrantLock(true);
for (int i = 0; i < 3; i++) {
new Thread(){
@Override
public void run() {
for (int j = 0; j < 2; j++) {
lock.lock();
System.out.println(Thread.currentThread().getName());
lock.unlock();
}
}
}.start();
}
}
}
```java
package com.xxxx.reids.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/***
* @title ReentrantLockFair
* @desctption 非公平鎖
* @author Kelvin
* @create 2023/5/29 16:10
**/
public class ReentrantLockFair {
public static void main(String[] args) {
//false表示非公平鎖
Lock lock = new ReentrantLock(false);
for (int i = 0; i < 3; i++) {
new Thread(){
@Override
public void run() {
for (int j = 0; j < 2; j++) {
lock.lock();
System.out.println(Thread.currentThread().getName());
lock.unlock();
}
}
}.start();
}
}
}
2、可重入鎖
可重入鎖又名遞歸鎖,是指在同一個(gè)線程在外層方法獲取鎖的時(shí)候,在進(jìn)入內(nèi)層方法會(huì)自動(dòng)獲取鎖(就是可以重新進(jìn)入獲得鎖)對于Java ReentrantLock而言, 其名字是Reentrant Lock即是重新進(jìn)入鎖。對于synchronized而言,也是一個(gè)可重入鎖??芍厝腈i的一個(gè)好處是可一定程度避免死鎖
(可重入鎖,指的是以線程為單位,當(dāng)一個(gè)線程獲取對象鎖之后,這個(gè)線程可以再次獲取本對象上的鎖,而其他的線程是不可以的。)
synchronized void setA() throws Exception{
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception{
Thread.sleep(1000);
}
/***
* @title ReentrantLockTest
* @desctption 可重入鎖測試
* @author Kelvin
* @create 2023/5/29 16:29
**/
public class ReentrantLockTest {
public static void main(String[] args) {
Object obj = new Object();
new Thread(() -> {
//第一次加鎖
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "第一層");
//第二次加鎖,此時(shí)obj對象處于鎖定狀態(tài),但是當(dāng)前線程仍然可以進(jìn)入,避免死鎖
synchronized (obj) {
int i = 1 / 0;
System.out.println(Thread.currentThread().getName() + "第二層");
}
}
}, "t1").start();
new Thread(() -> {
//第一次加鎖
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "第一層");
//第二次加鎖,此時(shí)obj對象處于鎖定狀態(tài),但是當(dāng)前線程仍然可以進(jìn)入,避免死鎖
synchronized (obj) {
System.out.println(Thread.currentThread().getName() + "第二層");
}
}
}, "t2").start();
}
}
練習(xí):用ReentrantLock測試可重入鎖
3、獨(dú)享鎖/共享鎖
獨(dú)享鎖是指該鎖一次只能被一個(gè)線程所持有;共享鎖是指該鎖可被多個(gè)線程所持有
對于Java ReentrantLock而言,其是獨(dú)享鎖。但是對于Lock的另一個(gè)實(shí)現(xiàn)類ReadWriteLock,其讀鎖是共享鎖,其寫鎖是獨(dú)享鎖。讀鎖的共享鎖可保證并發(fā)讀是非常高效的,讀寫、寫讀 、寫寫的過程是互斥的。獨(dú)享鎖與共享鎖也是通過AQS來實(shí)現(xiàn)的,通過實(shí)現(xiàn)不同的方法,來實(shí)現(xiàn)獨(dú)享或者共享。對于synchronized而言,當(dāng)然是獨(dú)享鎖。
(讀鎖使用共享模式;寫鎖使用獨(dú)占模式;讀鎖可以在沒有寫鎖的時(shí)候被多個(gè)線程同時(shí)持有,寫鎖是獨(dú)占的。當(dāng)有讀鎖時(shí),寫鎖就不能獲得;而當(dāng)有寫鎖時(shí),除了獲得寫鎖的這個(gè)線程可以獲得讀鎖外,其他線程不能獲得讀鎖)
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/***
* @title WriteAndReedLockTest
* @desctption 讀寫鎖
* @author kelvin
* @create 2023/5/29 16:58
**/
public class WriteAndReedLockTest {
private static ReentrantReadWriteLock reentrantReadWriteLock= new ReentrantReadWriteLock();
private static ExecutorService executorService = Executors.newFixedThreadPool(3);
//執(zhí)行三個(gè)線程進(jìn)行讀寫操作,并設(shè)置一個(gè)屏障,線程依次準(zhǔn)備就緒后未獲取鎖之前都在等待,當(dāng)?shù)谌齻€(gè)線程執(zhí)行 cyclicBarrier.await();后屏障解除,三個(gè)線程同時(shí)執(zhí)行。
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
private static Integer i = 100;
public static void main(String[] args) {
executorService.execute(
() ->{
read();
}
);
executorService.execute(
() ->{
write();
}
);
executorService.execute(
() ->{
read();
}
);
}
private static void read(){
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
//獲取讀鎖
reentrantReadWriteLock.readLock().lock();
System.out.println("Read," + Thread.currentThread().getName() + ",i = " + i);
reentrantReadWriteLock.readLock().unlock();
}
private static void write(){
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
//獲取寫鎖
reentrantReadWriteLock.writeLock().lock();
i ++;
System.out.println("Write," + Thread.currentThread().getName() + ",i = " + i);
reentrantReadWriteLock.writeLock().unlock();
}
}
4、互斥鎖/讀寫鎖
獨(dú)享鎖/共享鎖就是一種廣義的說法,互斥鎖/讀寫鎖就是具體的實(shí)現(xiàn)?;コ怄i在Java中的具體實(shí)現(xiàn)就是ReentrantLock;讀寫鎖在Java中的具體實(shí)現(xiàn)就是Read/WriteLock。
5、樂觀鎖/悲觀鎖
樂觀鎖與悲觀鎖不是指具體的什么類型的鎖,而是指看待并發(fā)同步的角度。
- 悲觀鎖:總是假設(shè)最壞的情況,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人會(huì)修改,所以每次在拿數(shù)據(jù)的時(shí)候都會(huì)上鎖,這樣別人想拿這個(gè)數(shù)據(jù)就會(huì)阻塞直到它拿到鎖。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機(jī)制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。再比如 Java 里面的同步原語 synchronized 關(guān)鍵字的實(shí)現(xiàn)也是悲觀鎖
- 樂觀鎖:顧名思義,就是很樂觀,每次去拿數(shù)據(jù)的時(shí)候都認(rèn)為別人不會(huì)修改,所以不會(huì)上鎖,但是在更新的時(shí)候會(huì)判斷一下在此期間別人有沒有去更新這個(gè)數(shù)據(jù),可以使用版本號(hào)等機(jī)制。樂觀鎖適用于多讀的應(yīng)用類型,這樣可以提高吞吐量,像數(shù)據(jù)庫提供的類似于 write_condition 機(jī)制,其實(shí)都是提供的樂觀鎖。在 Java中 java.util.concurrent.atomic 包下面的原子變量類就是使用了樂觀鎖的一種實(shí)現(xiàn)方式 CAS 實(shí)現(xiàn)的
- 在Java中java.util.concurrent.atomic包下面的原子變量類就是使用了樂觀鎖的一種實(shí)現(xiàn)方式CAS(Compare and Swap 比較并交換)實(shí)現(xiàn)的。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
/***
* @title AtomicExample
* @desctption 樂觀鎖/悲觀鎖
* @author Kelvin
* @create 2023/5/29 17:08
**/
public class AtomicExample {
private static Integer m = 1;
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executorService.submit(
() -> {
m ++;
}
);
}
TimeUnit.SECONDS.sleep(2);
System.out.println(m);
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/***
* @title AtomicExample
* @desctption 樂觀鎖/悲觀鎖
* @author Kelvin
* @create 2023/5/29 17:08
**/
public class AtomicExample {
private static AtomicInteger m = new AtomicInteger(1);
public static void main(String[] args) throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 1000; i++) {
executorService.submit(
() -> {
m.incrementAndGet();
}
);
}
TimeUnit.SECONDS.sleep(2);
System.out.println(m.get());
}
}
6、分段鎖
分段鎖其實(shí)是一種鎖的設(shè)計(jì),并不是具體的一種鎖,對于ConcurrentHashMap而言,其并發(fā)的實(shí)現(xiàn)就是通過分段鎖的形式來實(shí)現(xiàn)高效的并發(fā)操作,ConcurrentHashMap中的分段鎖稱為Segment,它即類似于HashMap(JDK7與JDK8中HashMap的實(shí)現(xiàn))的結(jié)構(gòu),即內(nèi)部擁有一個(gè)Entry數(shù)組,數(shù)組中的每個(gè)元素又是一個(gè)鏈表;同時(shí)又是一個(gè)ReentrantLock(Segment繼承了ReentrantLock)。當(dāng)需要put元素的時(shí)候,并不是對整個(gè)HashMap進(jìn)行加鎖,而是先通過hashcode來知道他要放在那一個(gè)分段中,然后對這個(gè)分段進(jìn)行加鎖,所以當(dāng)多線程put的時(shí)候,只要不是放在一個(gè)分段中,就實(shí)現(xiàn)了真正的并行的插入。但是,在統(tǒng)計(jì)size的時(shí)候,可就是獲取HashMap全局信息的時(shí)候,就需要獲取所有的分段鎖才能統(tǒng)計(jì)。
分段鎖的設(shè)計(jì)目的是細(xì)化鎖的粒度,當(dāng)操作不需要更新整個(gè)數(shù)組的時(shí)候,就僅僅針對數(shù)組中的一項(xiàng)進(jìn)行加鎖操作。
7、偏向鎖/輕量級(jí)鎖/重量級(jí)鎖
這三種鎖是指鎖的狀態(tài),并且是針對synchronized。在Java 5通過引入鎖升級(jí)的機(jī)制來實(shí)現(xiàn)高效synchronized。這三種鎖的狀態(tài)是通過對象監(jiān)視器在對象頭中的字段來表明的。文章來源:http://www.zghlxwxcb.cn/news/detail-464116.html
- 偏向鎖是指一段同步代碼一直被一個(gè)線程所訪問,那么該線程會(huì)自動(dòng)獲取鎖。降低獲取鎖的代價(jià)。
- 輕量級(jí)鎖是指當(dāng)鎖是偏向鎖的時(shí)候,被另一個(gè)線程所訪問,偏向鎖就會(huì)升級(jí)為輕量級(jí)鎖,其他線程會(huì)通過自旋的形式嘗試獲取鎖,不會(huì)阻塞,提高性能。
- 重量級(jí)鎖是指當(dāng)鎖為輕量級(jí)鎖的時(shí)候,另一個(gè)線程雖然是自旋,但自旋不會(huì)一直持續(xù)下去,當(dāng)自旋一定次數(shù)的時(shí)候,還沒有獲取到鎖,就會(huì)進(jìn)入阻塞,該鎖膨脹為重量級(jí)鎖。重量級(jí)鎖會(huì)讓其他申請的線程進(jìn)入阻塞,性能降低。
8、自旋鎖
在Java中,自旋鎖是指嘗試獲取鎖的線程不會(huì)立即阻塞,而是采用循環(huán)的方式去嘗試獲取鎖,這樣的好處是減少線程上下文切換的消耗,缺點(diǎn)是循環(huán)會(huì)消耗CPU文章來源地址http://www.zghlxwxcb.cn/news/detail-464116.html
到了這里,關(guān)于《微服務(wù)實(shí)戰(zhàn)》 第二十六章 Java鎖的分類的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!