生產環境中,併發場景是十分常見的,多個線程的併發天然會難以免的訪問到共享資源。而一旦出現多個線程共享同一個資源的時候,就會出現數據混亂的問題。最多見的就是先判斷後操做的場景,舉個例子,小明和小黃吃壞了肚子,想要上廁所,結果他倆同時看到一個空的位置,因而都認爲沒人本身能夠上廁所,結果兩我的都擠在了廁所裏。那麼該如何解決這個問題呢,沒錯,咱們能夠加上一把鎖,小明上廁所的時候把門關上,小黃阻塞着等待。經過將併發變爲按序單一執行,從而解決數據混亂的問題。java
下面咱們就介紹一下經常使用的樂觀鎖和悲觀鎖,以及在Java中的應用。mysql
樂觀鎖:顧名思義老是樂觀的認爲這個數據沒有在同一時間被其餘人操做變動sql
實現:CAS(compare and set)數據庫
優勢:吞吐量高,適用於數據衝突相對較少的場景多線程
缺點:受限於外部系統,可能會引發髒讀,且在數據衝突很大的場景下,性能反而可能更低併發
咱們主要介紹樂觀鎖思想的實現CAS,即compare and set,先比較再設置。仍是以以前小明小黃爲例,即在上廁所前先作一次和以前認爲的狀況作一次比較(以前認爲沒人),若是一致則上廁所。核心思路就是,每次不加鎖而是假設沒有衝突而去完成某項操做,若是由於衝突失敗就重試,直到成功爲止。下面咱們介紹一下CAS在java中的實現性能
AtomicInteger & AtomicReference<T>spa
AtomicInteger & AtomicReference<T>這兩個類,相比你們都很熟悉。Java中提供了這些原子類,來解決在多線程併發場景下共享數據的混亂問題,而其實現思想即爲CAS。而其底層實現的native方法依賴於外部系統,利用JNI來完成CPU指令的操做。咱們來分析一下代碼便可知線程
ABA問題3d
當時光是這樣如此還存在着一個問題,假設線程2將值A變爲B以後又將B變爲A,此時線程2過來發現值仍是A,單其實並非原來的A了,卻仍將其值變爲了C。咱們經過代碼的方式來看一下。
面對這種問題,咱們能夠設置一個版本號,在比對值的同時,在增長對版本號的比對。以此來解決ABA的問題,在Java提供了AtomicStampedReference類來實現帶版本號的cas操做。
數據庫實現樂觀鎖
除了利用Java底層依賴CPU指令來完成CAS操做,咱們還能夠借用數據庫來實現。其核心思想仍然與上面的相同,實現的核心在於如下的sql
update goods set num= newnum, version = oldversion+1 where version = oldversion
悲觀鎖:認爲數據在同一時刻總會被修改
優勢:更嚴格的避免數據混亂問題
缺點:性能消耗大
在Java中能夠直接經過關鍵字syncrhoized來實現。syncrhoized是一種獨佔鎖,當一個線程獲取到該鎖以後,其餘線程就會掛起,一直等待鎖釋放以後,纔會繼續執行。這樣就能保證在併發的狀況下數據不會混亂。可是相應的,其對性能的損耗也較大。
在mysql中可使用select…for update實現悲觀鎖。這樣那條數據就被咱們鎖定了,其它的事務必須等本次事務提交以後才能執行。從而保證數據不會被其餘事務更改從而致使數據的異常。可是select…for update不會阻塞select的查詢。
須要注意的是mysql在採用InnoDB時,默認爲行鎖,且只有明確額指定主鍵,MySQL 纔會執行行鎖,鎖住對應的那條數據,不然MySQL 將會執行表鎖(將整個數據表單給鎖住)。