java各種鎖
點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號(hào)”
優(yōu)質(zhì)文章,第一時(shí)間送達(dá)
76套java從入門(mén)到精通實(shí)戰(zhàn)課程分享
java有哪些鎖的分類(lèi):
樂(lè)觀鎖悲觀鎖:
什么是悲觀鎖,什么是樂(lè)觀鎖
悲觀鎖:
mysql的角度分析: 悲觀鎖就是比較悲觀,當(dāng)多個(gè)線(xiàn)程同一個(gè)數(shù)據(jù)實(shí)現(xiàn)修改的時(shí)候,最后只有一個(gè)線(xiàn)程才能修改成功,只要誰(shuí)能夠獲取到行鎖 則其他線(xiàn)程時(shí)不能夠?qū)?shù)據(jù)做任何修改操作,且是阻塞狀態(tài)
java鎖層面:如果沒(méi)有獲取到鎖,則會(huì)阻塞等待,后期喚醒的鎖成本就會(huì)非常高,從新被我們CPU從就緒調(diào)度為運(yùn)行狀態(tài)
Lock synchronized 鎖 悲觀鎖 沒(méi)有獲取到鎖的線(xiàn)程會(huì)阻塞等待;

樂(lè)觀鎖:
樂(lè)觀鎖比較樂(lè)觀,通過(guò)預(yù)值或者版本號(hào)比較,如果不一致性的情況則通過(guò)循環(huán)控制修改,當(dāng) 前線(xiàn)程不會(huì)被阻塞,是樂(lè)觀,效率比較高,但是樂(lè)觀鎖比較消耗 cpu 的資源。

樂(lè)觀鎖:獲取鎖–如果沒(méi)有獲取到鎖,當(dāng)前線(xiàn)程是不會(huì)阻塞等待,通過(guò)死循環(huán)控制
樂(lè)觀鎖屬于無(wú)鎖機(jī)制,沒(méi)有競(jìng)爭(zhēng)鎖的流程
注意:mysql 的 innodb 引擎中存在行鎖的概念/
Mysql層面如何實(shí)現(xiàn)樂(lè)觀鎖了?
在我們表結(jié)構(gòu)中,會(huì)新增一個(gè)字段就是版字段
``version varchar(255) DEFAULT NULL,`
多線(xiàn)程對(duì)同一行數(shù)據(jù)實(shí)現(xiàn)修改操作,提前查詢(xún)當(dāng)前最新的version版本號(hào)碼
作為update條件查詢(xún),如果當(dāng)前version版本號(hào)碼發(fā)生了變化,則查詢(xún)不到該數(shù)據(jù),
表示如果修改數(shù)據(jù)失敗,則不斷重試,有從新查詢(xún)最新的版本實(shí)現(xiàn)update
需要注意的是 控制樂(lè)觀鎖的循環(huán)的次數(shù),避免cpu飆高的問(wèn)題
mysql的innodb引擎中存在行鎖的概念
樂(lè)觀鎖的實(shí)現(xiàn)方式:
二

公平鎖與非公平鎖
公平鎖與非公平鎖的直接的區(qū)別
公平鎖: 就是比較公平,根據(jù)請(qǐng)求鎖的順序排列,先來(lái)請(qǐng)求先來(lái)請(qǐng)求的就先獲取鎖,后來(lái)獲取鎖就最 后獲取到, 采取隊(duì)列存放…類(lèi)似類(lèi)似于吃飯排隊(duì)。
隊(duì)列---底層實(shí)現(xiàn)方式---數(shù)組或者鏈表實(shí)現(xiàn)

/**
* 獨(dú)占鎖(寫(xiě)鎖) 一次只能被一個(gè)線(xiàn)程占有
* 共享鎖(讀鎖) 多個(gè)線(xiàn)程可以同時(shí)占有
* ReadWriteLock
* 讀-讀 可以共存!
* 讀-寫(xiě) 不能共存!
* 寫(xiě)-寫(xiě) 不能共存!
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
// 寫(xiě)入
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.put(temp + "", temp + "");
}, String.valueOf(i)).start();
}
// 讀取
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.get(temp + "");
}, String.valueOf(i)).start();
}
}
}
// 加鎖的
class MyCacheLock {
private volatile Map<String, Object> map = new HashMap<>();
// 讀寫(xiě)鎖: 更加細(xì)粒度的控制
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private Lock lock = new ReentrantLock();
// 存,寫(xiě)入的時(shí)候,只希望同時(shí)只有一個(gè)線(xiàn)程寫(xiě)
public void put(String key, Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "寫(xiě)入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "寫(xiě)入OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
// 取,讀,所有人都可以讀!
public void get(String key) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "讀取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "讀取OK");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
/**
* 自定義緩存
*/
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
// 存,寫(xiě)
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "寫(xiě)入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "寫(xiě)入OK");
}
// 取,讀
public void get(String key) {
System.out.println(Thread.currentThread().getName() + "讀取" + key);
Object o = map.get(key);
System.out.println(Thread.currentThread().getName() + "讀取OK");
}
}
可重入性
在同一個(gè)線(xiàn)程中鎖可以不斷傳遞的,可以直接獲取Syn/lock ``aqs
CAS(自旋鎖)
CAS: 沒(méi)有獲得到鎖的線(xiàn)程是不會(huì)阻塞的,通過(guò)循環(huán)控制一直不斷的獲取鎖
CAS:Compare and Swap,翻譯成比較并交換,執(zhí)行函數(shù)CAS(V,E,N)
三個(gè)操作數(shù): 內(nèi)存值V, 舊的預(yù)期值E,要修改的新值N當(dāng)且僅當(dāng)預(yù)期值E和內(nèi)存值V相同時(shí),將內(nèi)存值V修改為N,負(fù)責(zé)什么都不做

(1)CAS是通過(guò)硬件指令,保證了原子性
(2)java是通過(guò)unsafe jni技術(shù)
原子類(lèi): AtomicBoolean,AtomicInteger,AtomicLong等使用 CAS 實(shí)現(xiàn)
unsafe類(lèi)

優(yōu)點(diǎn):
沒(méi)有獲取到鎖的資源,會(huì)一直子啊用戶(hù)態(tài),不會(huì)阻塞,沒(méi)有鎖的線(xiàn)程會(huì)一直通過(guò)循環(huán)控 制重試
缺點(diǎn):
通過(guò)死循環(huán)控制,消耗 cpu 資源比較高,需要控制循次數(shù),避免 cpu 飆高問(wèn)題;
Cas 本質(zhì)的原理:
舊的預(yù)期值===>>>V(共享變量中值),才會(huì)修改我們的V
基于 cas 實(shí)現(xiàn)鎖機(jī)制原理
Cas 無(wú)鎖機(jī)制原理:
定義一個(gè)鎖的狀態(tài)
狀態(tài)狀態(tài)值=0,則表示沒(méi)有線(xiàn)程獲取到該鎖
狀態(tài)狀態(tài)值=1,則表示有線(xiàn)程已經(jīng)持有該鎖
實(shí)現(xiàn)細(xì)節(jié):
CAS獲取鎖:
將該鎖的狀態(tài)從0到1----能夠修改成功 cas 成功則表示獲取鎖成功
如果獲取鎖失敗–修改失敗,則不會(huì)阻塞而是通過(guò)循環(huán)==(自旋控制重試)==
CAS 釋放鎖:
將該鎖的狀態(tài)從 1 改為 0 如果能夠改成功 cas 成功則表示釋放鎖成功。
CAS 如何解決 ABA 的問(wèn)題
CAS主要檢查內(nèi)存值V 與舊的預(yù)值值=E是否一致,如果一致的情況下,則修改
這時(shí)會(huì)存在ABA的問(wèn)題:
如果將原來(lái)的值A(chǔ),改為B,B有改為了A發(fā)現(xiàn)沒(méi)有發(fā)生變化,實(shí)際上已經(jīng)發(fā)生了變化,所以存在ABA問(wèn)題
解決方法,通過(guò)版本號(hào),對(duì)沒(méi)個(gè)變量更新的版本號(hào)+1
引用原子引用,對(duì)應(yīng)的思想:樂(lè)觀鎖
解決 aba 問(wèn)題是否大:概念產(chǎn)生沖突,但是不影響結(jié)果,換一種方式 通過(guò)版本號(hào)碼方式
package com.nie.juc.cas;/*
*
*@auth wenzhao
*@date 2021/4/25 17:19
*/
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 演示 aba 的問(wèn)題
* (1)第一個(gè)參數(shù) expectedReference:表示預(yù)期值。
* (2)第二個(gè)參數(shù) newReference:表示要更新的值。
* (3)第三個(gè)參數(shù) expectedStamp:表示預(yù)期的時(shí)間戳。
* (4)第四個(gè)參數(shù) newStamp:表示要更新的時(shí)間戳。
*/
public class CASDemo {
//AtomicStampedReference 注意,如果泛型是一個(gè)包裝類(lèi),注意對(duì)象的引用問(wèn)題
// 注意:如果引用類(lèi)型是 Long、Integer、Short、Byte、Character 一定一定要注意值的緩存區(qū)間!
// 比如 Long、Integer、Short、Byte 緩存區(qū)間是在-128~127,
// 會(huì)直接存在常量池中,而不在這個(gè)區(qū)間內(nèi)對(duì)象的值 則會(huì)每次都 new 一個(gè)對(duì)象,
// 那么即使兩個(gè)對(duì)象的值相同,CAS 方法都會(huì)返回 false
// 先聲明初始值,修改后的值和臨時(shí)的值是為了保證使用 CAS 方法不會(huì)因?yàn)閷?duì)象不一樣而返回 false
// 正常在業(yè)務(wù)操作,這里面比較的都是一個(gè)個(gè)對(duì)象
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1);
// CAS compareAndSet : 比較并交換!
public static void main(String[] args) {
;
new Thread(() -> {
int stamp = atomicStampedReference.getStamp(); // 獲得版本號(hào)
System.out.println("a1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
Lock lock = new ReentrantLock(true);
atomicStampedReference.compareAndSet(1, 2,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println("a2=>----" + atomicStampedReference.getStamp());
System.out.println("更改" + atomicStampedReference.compareAndSet(2, 1,
atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("a3=>" + atomicStampedReference.getStamp());
}, "a").start();
// 樂(lè)觀鎖的原理相同!
new Thread(() -> {
int stamp = atomicStampedReference.getStamp(); // 獲得版本號(hào)
System.out.println("b1=>" + stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("+++++++++++++++++++++++++++++++++++++++++++++");
System.out.println(atomicStampedReference.compareAndSet(1, 6,
stamp, stamp + 1));
System.out.println("b2=>" + atomicStampedReference.getStamp());
}, "b").start();
}
}
輸出:
a1=>1
b1=>1
a2=>----2
更改true
a3=>3
+++++++++++++++++++++++++++++++++++++++++++++
false
b2=>3
利用原子類(lèi)手寫(xiě) CAS 無(wú)鎖
package com.nie.juc.cas;/*
*
*@auth wenzhao
*@date 2021/4/25 20:36
*/
import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.IntStream;
public class AtomicTryLock {
private AtomicLong cas = new AtomicLong(0);
private Thread lockCurrentThread;
/**
* 1 表示鎖已經(jīng)被獲取
* 0 表示鎖沒(méi)有獲取
* 利用 cas 將 0 改為 1 成功則表示獲取鎖
*
* @return
*/
//加鎖
private boolean tryLock() {
boolean result = cas.compareAndSet(0, 1);
if (result) {
lockCurrentThread = Thread.currentThread();
}
return result;
}
//釋放鎖
private boolean unLock() {
if (lockCurrentThread != Thread.currentThread()) {
return false;
}
return cas.compareAndSet(0, 1);
}
public static void main(String[] args) {
AtomicTryLock atomicTryLock = new AtomicTryLock();
IntStream.range(1, 10).forEach((i) -> new Thread(() ->
{
try {
boolean result = atomicTryLock.tryLock();
if (result) {
atomicTryLock.lockCurrentThread = Thread.currentThread();
System.out.println(Thread.currentThread().getName() + ",獲取鎖成功~");
} else {
System.out.println(Thread.currentThread().getName() + ",獲取鎖失敗~");
}
} catch (Exception e) {
} finally {
//釋放鎖
if (atomicTryLock != null) {
atomicTryLock.unLock();
}
}
}).start());
}
}
輸出
Thread-0,獲取鎖成功~
Thread-3,獲取鎖失敗~
Thread-2,獲取鎖失敗~
Thread-1,獲取鎖失敗~
Thread-4,獲取鎖失敗~
Thread-7,獲取鎖失敗~
Thread-8,獲取鎖失敗~
Thread-6,獲取鎖失敗~
Thread-5,獲取鎖失敗~

————————————————
版權(quán)聲明:本文為CSDN博主「面相薪水編程」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請(qǐng)附上原文出處鏈接及本聲明。
原文鏈接:
https://blog.csdn.net/qq_44236958/article/details/116138494
粉絲福利:Java從入門(mén)到入土學(xué)習(xí)路線(xiàn)圖
??????

??長(zhǎng)按上方微信二維碼 2 秒
感謝點(diǎn)贊支持下哈 
