一、悲觀鎖
比較悲觀,擔心拿數據時被別人修改,所以查詢時先加鎖在修改,保證操作時別人修改不了,期間需要訪問該數據的都會等待。
select version from user where id=1 for update
update user set version=2 where id=1
在對id = 1的記錄修改前,先通過for update的方式進行加鎖,然後再進行修改。這就是比較典型的悲觀鎖策略。
1.共享鎖
又稱爲讀鎖,可以查看但無法修改和刪除的一種數據鎖。(讀取)操作創建的鎖。其他用戶可以併發讀取數據,
但不能修改,增加,刪除數據。資源共享。
select name from user where id=1 lock in share mode
2.排他鎖
又稱爲寫鎖,其他線程對該記錄的更新與刪除操作都會阻塞等待。
select version from user where id=1 for update
二、樂觀鎖
比較樂觀,每次拿數據的時候都完全不擔心會被別人修改,所以不會上鎖,但是在更新數據的時候去判斷該期間是否被
別人修改過(使用版本號等機制,靠表設計和代碼來實現)
select version from user where id=1
result version=1
update user set version=2 where id=1 and version=1
我們在更新之前,先查詢一下庫存表中當前版本號,然後在做update的時候,以版本號作爲一個修改條件,
當我們提交更新的時候,判斷數據庫表對應記錄的當前庫存數與第一次取出來的庫存數進行比對,如果數據
庫表當前庫存數與第一次取出來的庫存數相等,則予以更新,否則認爲是過期數據。
悲觀鎖:用於寫比較多的情況,避免了樂觀鎖不斷重試從而降低性能
樂觀鎖:用於讀比較多的情況,避免了不必要的加鎖的開銷
三、CAS與synchronized
CAS屬於樂觀鎖,適用於寫比較少的情況,衝突較少
synchronized屬於悲觀鎖,適用於衝突寫比較多的情況,
衝突較多競爭較少的場景:synchronized會阻塞和喚醒線程並在用戶態和內核態切換浪費消耗cpu資源。
CAS基於硬件實現,不需要進入內核,不需要切換線程,操作自旋機率較少,因此可以獲得更高的性能。
競爭嚴重的場景:CAS自旋的概率會比較大,從而浪費更多的CPU資源,效率低於synchronized。
阿里巴巴java開發手冊:如果線程訪問衝突小於20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數不小於3次。
四、分佈式鎖
synchronized 只是本地鎖,鎖的也只是當前jvm下的對象,在分佈式場景下,要用分佈式鎖。
package com.kero99.utils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import redis.clients.jedis.Jedis;
@RestController
@Component
public class RedisLock {
/**
* @author ygc
* jMeter 線程組工具
* 壓力測試工具 http://coolaf.com/tool/testing
*/
//redis連接池 old 高併發場景會掛掉
// Jedis redis = RedisPool.getJedis();
//now
@Autowired
private RedisOperator redis;
//死鎖 超時時間 加鎖 業務邏輯 釋放鎖
@RequestMapping("/redisLock")
public String redisLock() {
//synchronized 只是本地鎖,鎖的也只是當前jvm下的對象,在分佈式場景下,要用分佈式鎖。
// synchronized(this) {
//redis分佈式鎖
// String uuid=UuidUtil.getUUID32();
// Long lock=redis.setnx("lock", uuid);
// redis.expire(String.valueOf(lock), 30);
// try {
// if(lock<=0) {
// return "error";
// }
System.out.println(redis.get("stock"));
int stock =Integer.parseInt(redis.get("stock"));
if(stock>0) {
stock=stock-1;
redis.set("stock", stock+"");
System.out.println("扣減成功,庫存stock"+stock);
}else {
System.out.println("減免失敗 庫存不足"+stock);
}
// } catch (Exception e) {
// e.printStackTrace();
// }finally {
// //高併發場景 線程安全保證原子性操作
// if(uuid.equals(redis.get("lock"))) {
// //出現異常報錯
// redis.del("lock"); //釋放鎖
// }
// }
// }
return "end";
}
}
五、鎖降級
鎖降級指的是寫鎖降級成爲讀鎖。如果當前線程擁有寫鎖,然後將其釋放,最後再獲取讀鎖,這種分段完成的過程不能稱之爲鎖降級。鎖降級是指把持住(當前擁有的)寫鎖,再獲取到讀鎖,隨後釋放(先前擁有的)寫鎖的過程。
package com.kero99.utils;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 鎖降級
* @author ygc
*
*/
public class Test1 {
public static void main(String[] args) {
lockData();
}
public static void lockData() {
boolean update=false;
ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
Lock writeLock = reentrantReadWriteLock.writeLock();
Lock readLock = reentrantReadWriteLock.readLock();
readLock.lock();
//update爲false時未更新
if(!update) {
//必須先釋放讀鎖
readLock.unlock();
writeLock.lock();
try {
if(!update) {
//數據流程
System.out.println("更新數據處理..");
//更新完成
update=true;
}
readLock.lock();
}finally {
writeLock.unlock();
}
//鎖降級完成,寫鎖降級爲讀鎖
}
try {
//數據流程
System.out.println("讀取數據處理..");
} finally {
// TODO: handle finally clause
readLock.unlock();
}
}
}