鎖機制:
樂觀鎖:1)通過版本號來實現,先查詢獲取版本號,在更新的時候校驗版本號並修改。
悲觀鎖:同步關鍵字就是悲觀鎖,也稱爲排它鎖。
樂觀鎖還讓用戶查詢當前版本號,悲觀鎖如果不釋放,查都不讓查詢。
樂觀鎖存在多種實現方式:mysql數據庫版本號,redis實現,CAS實現等。
在併發情況下,使用鎖機制,防止爭搶資源。
悲觀鎖是對數據的修改持悲觀態度(認爲數據在被修改的時候一定會存在併發問題),因此在整個數據處理過程中將數據鎖定。悲觀鎖的實現,往往依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,否則,即使在應用層中實現了加鎖機制,也無法保證外部系統不會修改數據)
鎖機制是爲了解決高併發問題。
使用悲觀鎖的原理就是,當我們在查詢出goods信息後就把當前的數據鎖定,直到我們修改完畢後再解鎖。
要使用悲觀鎖,我們必須關閉mysql數據庫的自動提交屬性。
set autocommit=0;
關閉了mysql的autocommit,所以需要手動控制事務的提交。
使用select…for update會把數據給鎖住,不過我們需要注意一些鎖的級別,MySQL InnoDB默認Row-Level Lock,所以只有「明確」地指定主鍵,MySQL 纔會執行Row lock (只鎖住被選取的數據) ,否則MySQL 將會執行Table Lock (將整個數據表單給鎖住)。
如果無主鍵或者主鍵不明確,會鎖住整個表。
-- 商品表
CREATE TABLE t_goods (
id int not null AUTO_INCREMENT comment 'id信息',
name VARCHAR(20) comment '商品名稱',
status int comment '商品狀態',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品表';
-- 訂單表
CREATE TABLE t_orders (
id int not null AUTO_INCREMENT comment 'id信息',
goods_id int comment '商品id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='訂單表';
insert into t_goods(name,status) values('無極書',1)
insert into t_orders(name,status) values('太極',1)
-- 在高併發環境下,這三行代碼可能存在問題
select status from t_goods where id=1;
insert into t_orders(goods_id) values(1);
update t_goods set status=2 where id=1;
-- 關閉事務
set autocommit=0;
start TRANSACTION;
select status from t_goods where id=1 for update;
insert into t_orders(goods_id) values(1);
update t_goods set status=2 where id=1;
commit;
set autocommit =0;
select status from t_goods where id=1 for update;
commit;
set autocommit=0;
SELECT * from t_goods where id=4 for update;
set autocommit=0;
SELECT * from t_goods where status=1 for update;
commit;
set autocommit=0;
SELECT * from t_goods where id>1 for update;
沒有提交事務導致行鎖住 還是因爲select…for update 鎖住數據?
不是因爲事務,而是因爲select…for update 鎖住數據。
樂觀鎖,使用版本標識來確定讀到的數據與提交時的數據是否一致。提交後修改版本標識,不一致時可以採取丟棄和再次嘗試的策略。
1、悲觀鎖,前提是,一定會有併發搶佔資源,強行獨佔資源,在整個數據處理過程中,將數據處於鎖定狀態。獨佔鎖其實就是一種悲觀鎖,排它鎖。
2、樂觀鎖,前提是,不會發生併發搶佔資源,只有在提交操作的時候檢查是否違反數據完整性。只能防止髒讀後數據的提交,不能解決髒讀。
Java裏面進行多線程通信的主要方式就是共享內存的方式,共享內存主要的關注點有兩個:可見性和有序性。加上覆合操作的原子性,我們可以認爲Java的線程安全性問題主要關注點有3個:可見性、有序性和原子性。
Java內存模型(JMM)解決了可見性和有序性的問題,而鎖解決了原子性的問題。
Redis的事務機制以及watch指令(CAS)實現樂觀鎖。
所謂樂觀鎖,就是利用版本號比較機制,只是在讀數據的時候,將讀到的數據的版本號一起讀出來,當對數據的操作結束後,準備寫數據的時候,再進行一次數據版本號的比較,若版本號沒有變化,即認爲數據是一致的,沒有更改,可以直接寫入,若版本號有變化,則認爲數據被更新,不能寫入,防止髒寫。
基於redis實現樂觀鎖。
redis的事務,涉及到的指令,主要有multi,exec,discard。而實現樂觀鎖的指令,在事務基礎上,主要是watch指令.
multi和exec之間的指令。 鍵值對變化時,指令不執行。
利用watch指令,基於CAS機制,簡單的樂觀鎖。
watch指令在一次事務執行完畢後,即結束其生命週期。
基於redis的樂觀鎖,可以得出一個結論:
1. 樂觀鎖的實現,必須基於WATCH,然後利用redis的事務。
2. WATCH生命週期,只是和事務關聯的,一個事務執行完畢(執行了exec命令),相應的watch的生命週期即結束。
測試redis樂觀鎖機制,需要開啓兩個窗口。
incr 命令:對數值+1;
decr 命令:對數值-1;
Redis Decrby 命令將 key 所儲存的值減去指定的減量值。 decrby key 20
Redis實現樂觀鎖比較簡單,主要思路就是watch一個key的變動,並在watch和unwatch之間做一個類似事務操作,只有當事務操作成功,整體循環纔會跳出,當然,當操作期間watch的key變動時候,提交事務操作時候,事務操作將會被取消。
public void testRedisSyn(int clientName,String clientList) {
//redis中存儲商品數量爲(goodsNum:100)
String key = "goodsNum";
Jedis jedis = new Jedis("192.168.140.98", 6379);
jedis.auth("redis密碼");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (true) {
try {
jedis.watch(key);
System.out.println("顧客:" + clientName + "開始搶商品");
System.out.println("當前商品的個數:" + jedis.get(key));
//當前商品個數
int prdNum = Integer.parseInt(jedis.get(key));
if (prdNum > 0) {
//開啓事務,返回一個事務控制對象
Transaction transaction = jedis.multi();
//預先在事務對象中裝入要執行的操作
transaction.set(key, String.valueOf(prdNum - 1));
List<Object> exec = transaction.exec();
if (exec == null || exec.isEmpty()) {
//可能是watch-key被外部修改,或者是數據操作被駁回
System.out.println("悲劇了,顧客:" + clientName + "沒有搶到商品");
} else {
//這個命令是做啥的。//搶到商品記錄一下
jedis.sadd(clientList, clientName+"");
System.out.println("好高興,顧客:" + clientName + "搶到商品");
break;
}
}
} catch (NumberFormatException e) {
e.printStackTrace();
}finally {
jedis.unwatch();
}
}
}
1.在不成功的情況下,一般需要重試幾次,在重試的過程中每次循環都需要重新watch操作,因爲每次事務提交之後,watch操作都會失效。
2.在事務提交之後返回的結果對象分爲幾種情況
1)事務提交前,watch的key發生改變,返回的List對象並不是null,而是一個初始化後的空對象(size==0)
2)事務提交前,watch的key沒有改變,事務提交成功,返回的List對象中有一個"OK"的String對象。
如果是高併發場景,就使用樂觀鎖,因爲樂觀鎖性能比悲觀鎖好;悲觀鎖不適合高併發場景。
樂觀鎖的實現方式:數據庫,redis;版本號。
樂觀鎖的使用場景,悲觀鎖的使用場景。
synchronized是悲觀鎖,這種線程一旦得到鎖,其他需要鎖的線程就掛起的情況就是悲觀鎖。
CAS操作的就是樂觀鎖,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因爲衝突失敗就重試,直到成功爲止。
1、查詢時 獲取樂觀鎖的標記
2、只有更新的時候,纔會更新鎖
3、重試,重新獲取鎖,然後去更新。
鎖機制:樂觀鎖(版本號,CAS),悲觀鎖(同步鎖)
樂觀鎖的實現,以及樂觀鎖的使用場景。 使用DB實現樂觀鎖。
使用樂觀鎖進行下單減庫存的操作。
4、watch,監視鍵值對,作用時如果事務提交exec時發現監視的監視對發生變化,事務將被取消。