目錄:算法
1.爲何要使用鎖?數據庫
2.鎖的類型?緩存
1.爲何要使用鎖?安全
通俗的說就是多個線程,也能夠說多個方法同時對一個資源進行訪問時,若是不加鎖會形成線程安全問題。舉例:好比有兩張票,可是有5我的進來買,買了一張票數就減1,在他們進門的時候會判斷是否還有票,可是在他們進門的那一刻,票還一張都沒有買走。可是他們都已經進門了,過了是否有票的校驗了,因此最後票數爲被減成負3,顯然是不對的,由於票不能小於0,因此須要加一個鎖,在同一時刻只能有一我的進門去買票,也就是同一個資源同一個時刻只能有一個線程進行操做,這樣在第三我的進門的時候就能判斷出票數已經賣完了,不會產生票數成負數的狀況。app
2.鎖的類型jvm
1.重入鎖分佈式
重入鎖也叫遞歸鎖,外層的函數獲取鎖後,若是裏面的函數仍然有獲取鎖的代碼,裏面的函數就不用從新獲取鎖了。 好比:ReentrantLock 和 synchronizedide
好比:函數
public class Test implements Runnable { public synchronized void get() { System.out.println("name:" + Thread.currentThread().getName() + " get();"); set(); } public synchronized void set() { System.out.println("name:" + Thread.currentThread().getName() + " set();"); } @Override public void run() { get(); } public static void main(String[] args) { Test ss = new Test(); new Thread(ss).start(); new Thread(ss).start(); new Thread(ss).start(); } }
public class Test02 extends Thread { ReentrantLock lock = new ReentrantLock(); public void get() { lock.lock(); System.out.println(Thread.currentThread().getId()); set(); lock.unlock(); } public void set() { lock.lock(); System.out.println(Thread.currentThread().getId()); lock.unlock(); } @Override public void run() { get(); } public static void main(String[] args) { Test ss = new Test(); new Thread(ss).start(); new Thread(ss).start(); new Thread(ss).start(); } }
2.讀寫鎖性能
讀寫鎖:既是排他鎖,又是共享鎖。讀鎖,共享鎖,寫鎖:排他鎖
public class Cache { static Map<String, Object> map = new HashMap<String, Object>(); static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); static Lock r = rwl.readLock(); static Lock w = rwl.writeLock(); // 獲取一個key對應的value public static final Object get(String key) { r.lock(); try { System.out.println("正在作讀的操做,key:" + key + " 開始"); Thread.sleep(100); Object object = map.get(key); System.out.println("正在作讀的操做,key:" + key + " 結束"); System.out.println(); return object; } catch (InterruptedException e) { } finally { r.unlock(); } return key; } // 設置key對應的value,並返回舊有的value public static final Object put(String key, Object value) { w.lock(); try { System.out.println("正在作寫的操做,key:" + key + ",value:" + value + "開始."); Thread.sleep(100); Object object = map.put(key, value); System.out.println("正在作寫的操做,key:" + key + ",value:" + value + "結束."); System.out.println(); return object; } catch (InterruptedException e) { } finally { w.unlock(); } return value; } // 清空全部的內容 public static final void clear() { w.lock(); try { map.clear(); } finally { w.unlock(); } } public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { Cache.put(i + "", i + ""); } } }).start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { Cache.get(i + ""); } } }).start(); } }
3.悲觀鎖
老是假設最壞的狀況,每次取數據時都認爲其餘線程會修改,因此都會加鎖(讀鎖、寫鎖、行鎖等),當其餘線程想要訪問數據時,都須要阻塞掛起。能夠依靠數據庫實現,如行 鎖、讀鎖和寫鎖等,都是在操做以前加鎖,在Java中,synchronized的思想也是悲觀鎖。
4.樂觀鎖
在更新數據的時候會判斷其餘線程是否修改過數據,通常經過version版本號控制。
具體實現,就是在數據表加一個version字段,每次更新的時候都會與上一次取出數據的版本號作比較。
① 線程A 和 線程B 同時取出同一條數據,這是數據的version爲0.
② 線程A 取出數據的時候version = 0 , 這時候線程A走完業務,須要更新數據,這是會 update tabel set version = version + 1 where id = {id} and version = #{取數 據時的version}
③ 這時線程B 也走完業務去更新這條數據,這時執行update tabel set version = version + 1 where id = {id} and version = #{取出數據時的version} 這時候取出數據時 的version爲0可是線程這條數據的version已經爲1了。因此會更新失敗。在這個狀況下能夠寫一個循環,從新去出該條數據進行更新,知道這條數據更新成功爲止。
5.CAS無鎖機制
這種模式在多核cpu的狀況下,徹底沒有鎖競爭帶來的系統開銷,也沒有線程間頻繁調度帶來的開銷,所以,它要比基於鎖的方式擁有更優越的性能。
算法過程:CAS算法的過程是這樣:它包含三個參數CAS(V,E,N): V表示要更新的變量,E表示預期值,N表示新值。僅當V值等於E值時,纔會將V的值設爲N,若是V值和E值不 同,則說明已經有其餘線程作了更新,則當前線程什麼都不作。最後,CAS返回當前V的真實值。
6.分佈式鎖
在分佈式的場景下,若是是單數據庫的狀況下,某些場景下還能夠用樂觀鎖,大部分場景想在不用的jvm中保證數據的同步,安全問題,仍是須要使用緩存,數據,zookepper等實現分佈式鎖。