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

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

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

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

/**
* 獨(dú)占鎖(寫鎖) 一次只能被一個(gè)線程占有
* 共享鎖(讀鎖) 多個(gè)線程可以同時(shí)占有
* ReadWriteLock
* 讀-讀 可以共存!
* 讀-寫 不能共存!
* 寫-寫 不能共存!
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
// 寫入
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<>();
// 讀寫鎖: 更加細(xì)粒度的控制
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private Lock lock = new ReentrantLock();
// 存,寫入的時(shí)候,只希望同時(shí)只有一個(gè)線程寫
public void put(String key, Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "寫入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "寫入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<>();
// 存,寫
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "寫入" + key);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "寫入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è)線程中鎖可以不斷傳遞的,可以直接獲取Syn/lock ``aqs
CAS(自旋鎖)
CAS: 沒有獲得到鎖的線程是不會(huì)阻塞的,通過循環(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是通過硬件指令,保證了原子性
(2)java是通過unsafe jni技術(shù)
原子類: AtomicBoolean,AtomicInteger,AtomicLong等使用 CAS 實(shí)現(xiàn)
unsafe類

優(yōu)點(diǎn):
沒有獲取到鎖的資源,會(huì)一直子啊用戶態(tài),不會(huì)阻塞,沒有鎖的線程會(huì)一直通過循環(huán)控 制重試
缺點(diǎn):
通過死循環(huán)控制,消耗 cpu 資源比較高,需要控制循次數(shù),避免 cpu 飆高問題;
Cas 本質(zhì)的原理:
舊的預(yù)期值===>>>V(共享變量中值),才會(huì)修改我們的V
基于 cas 實(shí)現(xiàn)鎖機(jī)制原理
Cas 無鎖機(jī)制原理:
定義一個(gè)鎖的狀態(tài)
狀態(tài)狀態(tài)值=0,則表示沒有線程獲取到該鎖
狀態(tài)狀態(tài)值=1,則表示有線程已經(jīng)持有該鎖
實(shí)現(xiàn)細(xì)節(jié):
CAS獲取鎖:
將該鎖的狀態(tài)從0到1----能夠修改成功 cas 成功則表示獲取鎖成功
如果獲取鎖失敗–修改失敗,則不會(huì)阻塞而是通過循環(huán)==(自旋控制重試)==
CAS 釋放鎖:
將該鎖的狀態(tài)從 1 改為 0 如果能夠改成功 cas 成功則表示釋放鎖成功。
CAS 如何解決 ABA 的問題
CAS主要檢查內(nèi)存值V 與舊的預(yù)值值=E是否一致,如果一致的情況下,則修改
這時(shí)會(huì)存在ABA的問題:
如果將原來的值A(chǔ),改為B,B有改為了A發(fā)現(xiàn)沒有發(fā)生變化,實(shí)際上已經(jīng)發(fā)生了變化,所以存在ABA問題
解決方法,通過版本號(hào),對(duì)沒個(gè)變量更新的版本號(hào)+1
引用原子引用,對(duì)應(yīng)的思想:樂觀鎖
解決 aba 問題是否大:概念產(chǎn)生沖突,但是不影響結(jié)果,換一種方式 通過版本號(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 的問題
* (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è)包裝類,注意對(duì)象的引用問題
// 注意:如果引用類型是 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();
// 樂觀鎖的原理相同!
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
利用原子類手寫 CAS 無鎖
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 表示鎖沒有獲取
* 利用 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從入門到入土學(xué)習(xí)路線圖
??????

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