日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

闭关修炼(六)各种锁

發(fā)布時(shí)間:2023/12/14 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 闭关修炼(六)各种锁 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

難者不會(huì),會(huì)者不難


文章目錄

  • java中的鎖你聽過(guò)哪些?
  • 悲觀鎖
    • 什么是悲觀鎖?
    • 悲觀鎖的缺點(diǎn)
  • 樂(lè)觀鎖
    • 什么是樂(lè)觀鎖?
  • 悲觀鎖和樂(lè)觀鎖的區(qū)別?
    • 使用場(chǎng)景的區(qū)別?
  • 重入鎖
    • 什么現(xiàn)象非重入鎖會(huì)產(chǎn)生而重入鎖不會(huì)產(chǎn)生?
    • 重入鎖有哪些?
    • 什么是重入鎖
    • 可重入鎖-synchronized例子
    • 可重入鎖-ReentrantLock例子
    • 重入鎖的好處?
    • 注意的小點(diǎn)
  • 讀寫鎖
    • 讀寫鎖的機(jī)制?
    • 例子
  • CAS無(wú)鎖機(jī)制
    • CAS是什么意思?
    • 什么是CAS無(wú)鎖機(jī)制?
    • 例子
  • 自旋鎖
    • 什么是自旋鎖
    • 例子
  • 分布式鎖


java中的鎖你聽過(guò)哪些?

悲觀鎖,樂(lè)觀鎖,分段鎖,重入鎖,讀寫鎖,CAS鎖,排他鎖(基本上是自帶的功能),自旋鎖,分布式鎖(muji)

悲觀鎖

什么是悲觀鎖?

老生常談的問(wèn)題。

悲觀鎖每次在拿數(shù)據(jù)時(shí),都會(huì)上鎖,自帶排他鎖的功能。

舉個(gè)具體的例子,有張Order表,里有id,orderId,state屬性,另外張Money表中,里有id,orderId,money屬性,現(xiàn)在有個(gè)業(yè)務(wù)執(zhí)行三條sql語(yǔ)句:
select * from order where orderId=1 and state=0,如果select到內(nèi)容繼續(xù)往下執(zhí)行:
update set state=1 where orderId=1,
update money set money=moeny+money where orderId=1

此時(shí)有兩個(gè)jdbc連接,同時(shí)執(zhí)行以上三條sql語(yǔ)句,會(huì)發(fā)生什么?產(chǎn)生重復(fù)讀問(wèn)題,都讀到state=0,select出了數(shù)據(jù),都繼續(xù)往下執(zhí)行了,導(dǎo)致money更新不對(duì),那么要如何解決呢?

使用悲觀鎖,在sql中將第一句改為select * from order where orderId=1 and state=0 for update,在查的時(shí)候,十分悲觀,整個(gè)事務(wù)只允許一個(gè)連接進(jìn)行操作,拿不到鎖的連接只能一直等著,等待拿到鎖的連接提交完事務(wù)釋放鎖資源。

悲觀鎖的缺點(diǎn)

因?yàn)橹荒鼙WC一個(gè)連接進(jìn)行操作,效率十分低。項(xiàng)目中查詢量比較大的情況下,不會(huì)使用悲觀鎖。

樂(lè)觀鎖

什么是樂(lè)觀鎖?

比較簡(jiǎn)單,表示比較樂(lè)觀,它在別人在做修改的時(shí)候,不會(huì)上鎖,不過(guò)加稱為版本標(biāo)識(shí)的判斷(類似CAS無(wú)鎖機(jī)制),主要特點(diǎn)是本質(zhì)上沒(méi)有鎖,使用版本標(biāo)識(shí)和影響行數(shù)(如version-版本號(hào))進(jìn)行控制,樂(lè)觀鎖有效的根本原因是sql執(zhí)行的原子性。

回到具體例子,還是有兩個(gè)連接執(zhí)行那三句sql:
select * from order where orderId=1 and state=0,如果select到內(nèi)容繼續(xù)往下執(zhí)行:
update set state=1 where orderId=1,
update money set money=moeny+money where orderId=1

使用樂(lè)觀鎖,Order表添加version字段,update的sql執(zhí)行改為
update set state=1,set version=version+1 where orderId=1 and version=version

當(dāng)一個(gè)連接執(zhí)行select語(yǔ)句,獲取version版本號(hào),update語(yǔ)句中where附加version進(jìn)行查找,修改完畢后將version+1,此時(shí)另外一個(gè)連接也select到了數(shù)據(jù),不過(guò)version是舊的,再根據(jù)舊的version就使用不了update語(yǔ)句了,此時(shí)它的影響行數(shù)為0。這里的影響行數(shù)就是數(shù)據(jù)庫(kù)返回給你的成功修改的行數(shù)值。

如果影響行數(shù)>0,執(zhí)行第三句sql,第一個(gè)連接成功修改了第二句update語(yǔ)句,因此它的影響行數(shù)大于0,可以執(zhí)行第三句sql。

悲觀鎖和樂(lè)觀鎖的區(qū)別?

使用場(chǎng)景的區(qū)別?

如果查詢量小,可以使用悲觀鎖,在請(qǐng)求量大時(shí),多個(gè)請(qǐng)求來(lái)時(shí),悲觀鎖只能讓一個(gè)請(qǐng)求執(zhí)行。

使用樂(lè)觀鎖使用版本控制操作,要使用樂(lè)觀鎖的話需要自己在表中添加一個(gè)版本標(biāo)識(shí)字段,常規(guī)下(絕大多數(shù))使用樂(lè)觀鎖。

重入鎖

什么現(xiàn)象非重入鎖會(huì)產(chǎn)生而重入鎖不會(huì)產(chǎn)生?

死鎖現(xiàn)象

具體見之前的死鎖例子,當(dāng)時(shí)已經(jīng)有用到重入的概念了。

重入鎖有哪些?

在Java中,ReentrantLock和synchronized都是可重入鎖。

什么是重入鎖

重入鎖,又稱為遞歸鎖,指的是同一線程的外層函數(shù)獲得鎖資源之后,內(nèi)層的遞歸函數(shù)仍然有該鎖使用權(quán)。

可重入鎖-synchronized例子

get中調(diào)用set方法,函數(shù)進(jìn)行嵌套,鎖能夠進(jìn)行傳遞

import lombok.SneakyThrows;class MyTThread implements Runnable {public void get() {System.out.println(Thread.currentThread().getId() + " get()");set();}@SneakyThrowspublic void set() {Thread.sleep(100);System.out.println(Thread.currentThread().getId() + " set()");}@Overridepublic void run() {get();} }public class Test2 {public static void main(String[] args) {MyTThread myTThread = new MyTThread();new Thread(myTThread).start();new Thread(myTThread).start();new Thread(myTThread).start();} }

這段代碼執(zhí)行結(jié)果是

11 get() 13 get() 12 get() 13 set() 11 set() 12 set()

我們希望get(),set()交替執(zhí)行,我們?cè)趦蓚€(gè)方法都加上synchronized關(guān)鍵字修飾即可

public synchronized void get() {System.out.println(Thread.currentThread().getId() + " get()");set();}@SneakyThrowspublic synchronized void set() {Thread.sleep(100);System.out.println(Thread.currentThread().getId() + " set()");}

那么是為什么呢?

這就是因?yàn)閟ynchronized是重入鎖,線程①進(jìn)行g(shù)et()方法后占用this鎖,調(diào)用set()時(shí)候,set方法仍然有this鎖的使用權(quán),而其他的線程②和③因?yàn)楂@取不到this鎖被阻塞,只能等待線程①的set()方法執(zhí)行完畢釋放鎖。

執(zhí)行結(jié)果如下:

11 get() 11 set() 13 get() 13 set() 12 get() 12 set()

舉一反三,使用同步代碼塊的時(shí)候,只要外層和內(nèi)層函數(shù)使用的是同一個(gè)鎖對(duì)象,那么就是可重入的;如果外層和內(nèi)層函數(shù)使用的是不是同一個(gè)鎖對(duì)象,那么就是非可重入的。

可重入鎖-ReentrantLock例子

這個(gè)例子和上一個(gè)例子是一樣的,不作過(guò)多解釋

import lombok.SneakyThrows; import java.util.concurrent.locks.ReentrantLock;class MyTThread implements Runnable {private ReentrantLock lock = new ReentrantLock();public synchronized void get() {lock.lock();System.out.println(Thread.currentThread().getId() + " get()");set();lock.unlock();}@SneakyThrowspublic synchronized void set() {lock.lock();Thread.sleep(100);System.out.println(Thread.currentThread().getId() + " set()");lock.unlock();}@Overridepublic void run() {get();} }public class Test2 {public static void main(String[] args) {MyTThread myTThread = new MyTThread();new Thread(myTThread).start();new Thread(myTThread).start();new Thread(myTThread).start();} }

重入鎖的好處?

外層函數(shù)能將鎖資源傳遞給內(nèi)層函數(shù),內(nèi)層函數(shù)無(wú)需重新獲取鎖資源,效率提高。

注意的小點(diǎn)

內(nèi)層函數(shù)釋放鎖資源不影響外層函數(shù)的鎖資源

讀寫鎖

考慮這樣一個(gè)場(chǎng)景,兩個(gè)線程對(duì)一個(gè)共享文件進(jìn)行讀寫操作,同時(shí)讀是沒(méi)有問(wèn)題的,但是如果有一個(gè)線程想去寫這個(gè)共享文件,那么就不應(yīng)該有其他的線程對(duì)該資源進(jìn)行讀或?qū)憽?/p>

讀寫鎖的機(jī)制?

無(wú)論多少個(gè)線程如果都在讀,其他的線程可以讀或?qū)?#xff0c;只要一個(gè)線程正在寫,其他線程不可以讀或?qū)憽?/p>

例子

首先是不加鎖的情況

import lombok.Data; import lombok.SneakyThrows;import java.util.HashMap; import java.util.Map;@Data class Cache {static private volatile Map<String, Object> map = new HashMap<>();@SneakyThrowspublic static Object write(String key, Object value){System.out.println("正在開始寫..." + ", key: " + key + ",value: " + value);Thread.sleep(100);Object o = map.put(key, value);System.out.println("結(jié)束寫..." + ", key: " + key + ",value: " + value);return o;}@SneakyThrowspublic static Object read(String key){System.out.println("正在開始讀..." + ", key: " + key);Thread.sleep(100);Object o = map.get(key);System.out.println("結(jié)束讀..." + ", key: " + key + ",value: " + o);return o;} }public class Test3 {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {Cache.write(i + "", i + "");}}}).start();new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {Cache.read(i + "");}}}).start();} }

運(yùn)行結(jié)果可以看到,9正在寫,還沒(méi)有結(jié)束寫,9就開始讀了,讀的結(jié)果是null,數(shù)據(jù)發(fā)生了異常

如何解決呢?使用ReentrantReadwriteLock-讀寫鎖,使用起來(lái)還是十分容易的。

import lombok.Data; import lombok.SneakyThrows;import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock;@Data class Cache {static private volatile Map<String, Object> map = new HashMap<>();static private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();// 讀鎖static Lock r = readWriteLock.readLock();// 寫鎖static Lock w = readWriteLock.writeLock();@SneakyThrowspublic static Object write(String key, Object value) {w.lock();System.out.println("正在開始寫..." + ", key: " + key + ",value: " + value);Thread.sleep(100);Object o = map.put(key, value);System.out.println("結(jié)束寫..." + ", key: " + key + ",value: " + value);w.unlock();return o;}@SneakyThrowspublic static Object read(String key) {r.lock();System.out.println("正在開始讀..." + ", key: " + key);Thread.sleep(100);Object o = map.get(key);System.out.println("結(jié)束讀..." + ", key: " + key + ",value: " + o);r.unlock();return o;} }public class Test3 {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {Cache.write(i + "", i + "");}}}).start();new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 10; i++) {Cache.read(i + "");}}}).start();} }

結(jié)果就不會(huì)出現(xiàn)寫一半的時(shí)候出現(xiàn)讀了

CAS無(wú)鎖機(jī)制

CAS是什么意思?

Compare And Swap

什么是CAS無(wú)鎖機(jī)制?

CAS無(wú)鎖機(jī)制和自旋鎖是配套使用的,因?yàn)樽孕i的底層用的就是CAS無(wú)鎖機(jī)制,CAS無(wú)鎖機(jī)制效率非常高,CAS無(wú)鎖機(jī)制其實(shí)和樂(lè)觀鎖是類似的概念,本身沒(méi)有鎖,而是用一個(gè)標(biāo)識(shí)。

CAS體系中有三個(gè)參數(shù),分別是V,E,N,V表示要更新的值,E表示期望值,N表示新值,線程執(zhí)行先判斷要更新的值V與期望值E,如果它們相同,說(shuō)明沒(méi)有任何線程更改,線程繼續(xù)操作,將新值N覆蓋V;如果V和E不同,說(shuō)明其他線程更改過(guò),當(dāng)前線程不做任何操作,只把N覆蓋V。

其實(shí)預(yù)期值E就是之前緩存的值,更新值V如果和預(yù)期值E不同的話,說(shuō)明V被其他線程修改了,再進(jìn)行操作共享數(shù)據(jù)將可能會(huì)發(fā)生沖突,所以不操作共享數(shù)據(jù),只把新值N賦給V。

例子

看AtomicInteger的源碼,

public class Test1 {public static void main(String[] args) {new AtomicInteger().incrementAndGet();} }

ctrl+左鍵點(diǎn)進(jìn)去,我發(fā)現(xiàn)我這里的源碼和之前不一樣了,應(yīng)該被重構(gòu)過(guò),改到更底層去實(shí)現(xiàn)了,但是再怎么底層原理應(yīng)該是不變的。

public final int incrementAndGet() {return unsafe.getAndAddInt(this, valueOffset, 1) + 1;}

ctrl+左鍵進(jìn)入unsafe.getAndAddInt,值得注意的是Unsafe中的方法是原子操作。

public final int getAndAddInt(Object var1, long var2, int var4) {int var5;do {var5 = this.getIntVolatile(var1, var2);} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));return var5;}

首先是var5 = this.getIntVolatile(var1, var2);表示通過(guò)對(duì)象和偏移量獲取變量的值,Unsafe類可以讓java操作內(nèi)存地址,就是比如這里通過(guò)對(duì)象和偏移量直接從內(nèi)存中獲取對(duì)應(yīng)變量的值,取得的值賦給var5,這里的var5其實(shí)是要更新的值V,由于用了Volatile修飾,因此V是線程之間可見的,就是說(shuō)不同的線程看到的V都是相同的一個(gè)值。

compareAndSwapInt方法是Java的native方法,并不由Java語(yǔ)言實(shí)現(xiàn)。
public final native boolean compareAndSwapInt(Object o, long offset,int expected, int x);

方法的作用是,讀取傳入對(duì)象o在內(nèi)存中偏移量為offset位置的值與期望值expected(上次緩存的V值)作比較。相等就把x(這里的x就是var5+var4,等同于V+1)值賦值給offset位置的值。方法返回true。不相等,就取消賦值,方法返回false,繼續(xù)執(zhí)行g(shù)etIntVolatile刷新V值,繼續(xù)和上次緩存了V值的E比較,直到在其它線程執(zhí)行CAS操作之前,搶先退出循環(huán)操作,執(zhí)行+1操作。
unsafe.getAndAddInt(this, valueOffset, 1) + 1;

自旋鎖

什么是自旋鎖

自旋鎖是采用讓當(dāng)前線程不停的在循環(huán)體內(nèi)執(zhí)行實(shí)現(xiàn)的,當(dāng)循環(huán)的條件被其他線程改變時(shí)才能進(jìn)入臨界區(qū)。是不可重入的鎖

例子

下面例子展示自旋鎖現(xiàn)象,效果是線程卡死。

當(dāng)?shù)谝粋€(gè)線程調(diào)用這個(gè)不可重入的自旋鎖去加鎖(調(diào)用lock函數(shù))是沒(méi)有問(wèn)題的,但當(dāng)再次調(diào)用lock的時(shí)候,因?yàn)樽孕i已經(jīng)持有引用已經(jīng)不為空了,該線程對(duì)象會(huì)誤認(rèn)為是別人的線程持有自旋鎖,釋放不了鎖資源,程序直接卡死。

自旋鎖使用了CAS原子操作(compareAndSet方法),lock函數(shù)將所有者owner設(shè)置為當(dāng)前線程,并且預(yù)測(cè)原來(lái)的值為空;unlock函數(shù)將所有者owner設(shè)置為空,并預(yù)測(cè)值為當(dāng)前對(duì)象。

當(dāng)有第二個(gè)線程調(diào)用lock方法時(shí),由于owner值不為空(設(shè)置為了其他或者當(dāng)前線程對(duì)象),導(dǎo)致循環(huán)一致被執(zhí)行,直至第一個(gè)線程調(diào)用了unlock函數(shù)將owner設(shè)為空,第二個(gè)線程才能進(jìn)入臨界區(qū)。

由于自旋鎖只是將當(dāng)前線程不停地執(zhí)行循環(huán)體,不進(jìn)行線程狀態(tài)的改變,所以響應(yīng)速度更快。但當(dāng)線程數(shù)不停增加時(shí),性能下降明顯,因?yàn)槊總€(gè)線程都要執(zhí)行,占用CPU時(shí)間

package ch6;import lombok.AllArgsConstructor; import lombok.Data;import java.sql.Statement; import java.util.concurrent.atomic.AtomicReference;class MySpinLock {// 原子類 作用是對(duì)對(duì)象的引用,它可以保證你在修改對(duì)象引用時(shí)的線程安全性private AtomicReference<Thread> sign = new AtomicReference<>();public void lock(){Thread current = Thread.currentThread();while (!sign.compareAndSet(null, current)){}}public void unlock(){Thread current = Thread.currentThread();sign.compareAndSet(current, null);} } @Data @AllArgsConstructor public class Test2 implements Runnable{static int sum;private MySpinLock lock;public static void main(String[] args) {MySpinLock lock = new MySpinLock();for (int i = 0; i < 10; i++) {Test2 test2 = new Test2(lock);Thread thread =new Thread(test2);thread.start();}}@Overridepublic void run() {this.lock.lock();this.lock.lock();sum++;this.lock.unlock();this.lock.unlock();} }

分布式鎖

//todo 先挖坑,挖坑填不填就不知道了

總結(jié)

以上是生活随笔為你收集整理的闭关修炼(六)各种锁的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。