来说一说你对锁都怎么分类?
悲觀鎖和樂(lè)觀鎖
我覺(jué)得悲觀鎖和樂(lè)觀鎖更多的是指一種思想,實(shí)現(xiàn)鎖的不同方式。數(shù)據(jù)庫(kù)、git都有不同的對(duì)應(yīng)悲觀鎖和樂(lè)觀鎖思想的應(yīng)用.
-
悲觀鎖一般是互斥鎖,
但是1.阻塞和喚醒會(huì)帶來(lái)性能損耗,要去切換用戶態(tài)切換狀態(tài),查看要喚醒的線程等等;2.而且可能導(dǎo)致永久阻塞,比如發(fā)生了無(wú)限循環(huán),死鎖,那么阻塞等待獲取鎖的線程就可能永久等待了。3.還可能導(dǎo)致線程優(yōu)先級(jí)混亂。適合并發(fā)多競(jìng)爭(zhēng)很激烈的情況,代碼復(fù)雜 或者循環(huán)量大java中最常見(jiàn)的悲觀鎖就是synchronized和lock類,但是注意,synchronized引入了偏向鎖、輕量級(jí)鎖等優(yōu)化措施,readWriteLock在讀的時(shí)候是共享鎖,寫的時(shí)候是獨(dú)占鎖。總體上來(lái)說(shuō)還是得先拿到鎖,才能執(zhí)行
-
樂(lè)觀鎖是非互斥鎖,
認(rèn)為自己在操作的時(shí)候不會(huì)有其他線程干擾,所以不會(huì)鎖住操作對(duì)象。更新的時(shí)候去再去比較對(duì)象數(shù)據(jù)是否被修改過(guò),如果沒(méi)修改過(guò)最好,如果發(fā)生了修改,就要選擇放棄,拋棄,重試等策略。適合并發(fā)寫入少,讀取多的情況,提高讀取的性能樂(lè)觀鎖基本都是基于CAS算法實(shí)現(xiàn)的,注意ABA問(wèn)題,可以加版本號(hào)解決。有原子類,并發(fā)容器
共享鎖和獨(dú)占鎖
獨(dú)占鎖 ,又稱排它鎖、獨(dú)享鎖
共享鎖,又稱讀鎖,獲取到鎖后可以查看但不能修改刪除。為什么要這樣設(shè)計(jì)呢 ?是因?yàn)楹芏嗑€程讀并不會(huì)造成線程安全問(wèn)題,所以如果允許多個(gè)線程來(lái)讀,就可以提高性能。
ReentrantReadWriteLock讀寫鎖,其中讀鎖是共享鎖,寫鎖時(shí)獨(dú)占鎖。
要么是多讀,要么是一寫。更具體點(diǎn)說(shuō)就是,多個(gè)線程讀沒(méi)問(wèn)題;以如果已經(jīng)有線程在讀,那么其他線程申請(qǐng)寫鎖則必須要等待釋放讀鎖;如果一個(gè)線程已經(jīng)在寫,那么其他線程申請(qǐng)讀或?qū)懚急仨氁却尫艑戞i。
ReentrantReadWriteLock公平鎖:不允許任何插隊(duì),不管寫鎖還是讀鎖,只要隊(duì)列里已經(jīng)有線程了就應(yīng)該阻塞等待
ReentrantReadWriteLock非公平:
- 寫鎖可以隨時(shí)插隊(duì),即不需要阻塞
- 讀鎖僅在等待隊(duì)列中頭結(jié)點(diǎn)不是寫線程時(shí)可以插隊(duì):
舉個(gè)例子:假設(shè)線程2和線程4正在同時(shí)讀,線程3想要寫入,所以進(jìn)入了等待隊(duì)列且在頭結(jié)點(diǎn)。然后線程5過(guò)來(lái)想要讀,那么此時(shí)允不允許線程5去同時(shí)讀呢?
如果允許線程5插隊(duì)讀,則可能不停的有線程來(lái)插隊(duì)讀,寫的線程就可能饑餓,所以ReentrantReadWriteLock不允許插隊(duì)讀,如果有寫線程在排隊(duì)則必須進(jìn)入隊(duì)列排隊(duì)
ReentrantReadWriteLock支持寫鎖降級(jí)為讀鎖,但不支持讀鎖升級(jí)為寫鎖(避免死鎖)。
為什么需要鎖降級(jí)?比如一個(gè)任務(wù)剛開(kāi)始需要寫鎖拿到某個(gè)日志文件,但是后續(xù)都只需要讀就行,顯然如果還是一直持有寫鎖性能就會(huì)差很多,如果可以降級(jí)為讀鎖,就能允許其他線程一起讀,性能就會(huì)好很多。
為什么不支持鎖升級(jí)為寫鎖呢?可能造成死鎖,比如兩個(gè)線程同時(shí)準(zhǔn)備升級(jí)為寫鎖
公平鎖和非公平鎖
公平鎖指的是完全按照線程請(qǐng)求的順序來(lái)分配鎖;非公平是不完全按照線程請(qǐng)求順序,注意不是完全隨機(jī)的,在一定情況下可以插隊(duì)。
為什么要設(shè)計(jì)非公平鎖呢?
非公平可以避免去喚醒線程時(shí)的空檔期,提高使用性能,提高吞吐量。但有可能造成線程饑餓,即某些線程一直都拿不到鎖
synchronize是非公平鎖,ReentrantLock默認(rèn)是非公平鎖,但也可以構(gòu)造公平鎖。其實(shí)對(duì)應(yīng)源碼的實(shí)現(xiàn)很簡(jiǎn)單,就是在獲取鎖時(shí)是否放入隊(duì)列
//非公平鎖 的嘗試獲取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) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}//公平鎖的 嘗試獲取protected final boolean tryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {//唯一的區(qū)別就在于這里,如果鎖沒(méi)被任何一個(gè)線程拿到,不像上面直接去cas爭(zhēng)搶,//而是會(huì)hasQueuedPredecessors去判斷是否有其他線程等待的時(shí)間更長(zhǎng)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;}注意tryLock是個(gè)特例sync.nonfairTryAcquire(1);,即使設(shè)置的公平鎖,也會(huì)非公平的去爭(zhēng)搶鎖。
可重入鎖和不可重入鎖
什么是可重入鎖呢?簡(jiǎn)單來(lái)說(shuō),就是一個(gè)線程可以多次拿到一個(gè)鎖,Reentrant和synchronize都是可重入鎖
有什么好處?
避免死鎖,如果不是可重入鎖,你拿到鎖了,然后你想進(jìn)入鎖的另一個(gè)方法,你拿不到了!可能造成死鎖
避免了重復(fù)的加鎖和解鎖
自旋鎖和阻塞鎖
也就是準(zhǔn)備獲取鎖的線程無(wú)法獲取到鎖時(shí),就先自旋,不用阻塞,避免了線程切換帶來(lái)的開(kāi)銷。但是可能帶來(lái)CPU的浪費(fèi),因此有自適應(yīng)自旋鎖。
synchronize引入的輕量級(jí)鎖就是自旋鎖的最好應(yīng)用。適合少量線程競(jìng)爭(zhēng),且每個(gè)線程持有鎖的時(shí)間不長(zhǎng)的情況
可中斷鎖和不可中斷鎖
如果某個(gè)線程獲取到了鎖正在執(zhí)行,線程B正在等待獲取鎖,可是由于等待時(shí)間過(guò)長(zhǎng),我們可以中斷線程B,這就是可中斷鎖。
synchronize就是不可中斷鎖,lock是可中斷鎖,try( time)和lockInterruptibly都能響應(yīng)中斷。
鎖優(yōu)化
- JVM提供了鎖粗化和鎖消除來(lái)優(yōu)化鎖
- 我們?cè)诓l(fā)編程時(shí)要注意,盡量縮小同步代碼塊的范圍,盡量不要鎖住方法,減少加鎖的次數(shù)。鎖中不要再包含鎖,容易造成死鎖。選擇合適的鎖和工具類
總結(jié)
以上是生活随笔為你收集整理的来说一说你对锁都怎么分类?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Mysql默认隔离级别为什么是可重复读?
- 下一篇: Linux下查看文件内容