可重入鎖:java
簡單來講,支持重複加鎖,有可重用性算法
特徵:鎖能夠傳遞,方法遞歸傳遞安全
目的:避免了死鎖現象多線程
代碼:併發
public class Test implements Runnable { @Override public void run() { method1(); } public synchronized void method1() { System.out.println("method1"); method2(); } public synchronized void method2() { System.out.println("method2"); } public static void main(String[] args) { new Thread(new Test()).start(); } }
打印:分佈式
method1
method2
分析:若是鎖不能重用,那麼這裏將會出現死鎖問題ide
使用ReentrantLock鎖:高併發
public class TestLock implements Runnable { //重入鎖 private Lock reentrantLock = new ReentrantLock(); @Override public void run() { method1(); } public void method1() { try { reentrantLock.lock(); System.out.println("method1"); method2(); } catch (Exception e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } } public void method2() { try { reentrantLock.lock(); System.out.println("method2"); } catch (Exception e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } } public static void main(String[] args) { new Thread(new TestLock()).start(); } }
讀寫鎖:atom
高併發的時候,寫操做的同時應當不容許讀操做spa
(多線程中:讀-讀共存;讀-寫、寫-寫都不能夠共存)
代碼:製造讀寫操做的線程安全問題
public class TestWriteLock { Map<String, String> cache = new HashMap<>(); //寫入元素 public void put(String key, String value) { try { System.out.println("開始寫入key : " + key + " value : " + value); Thread.sleep(50); cache.put(key, value); System.out.println("完成寫入key : " + key + " value : " + value); } catch (Exception e) { e.printStackTrace(); } } //讀取元素 public String get(String key) { System.out.println("開始讀取key : " + key); String value = cache.get(key); System.out.println("讀取成功key : " + key + " value : " + value); return value; } public static void main(String[] args) { TestWriteLock test = new TestWriteLock(); Thread readThread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { test.put("i", i + ""); } } }); Thread writeThread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { test.get("i"); } } }); readThread.start(); writeThread.start(); } }
觀察打印:發現不合理
開始寫入key : i value : 0 開始讀取key : i 讀取成功key : i value : null .................................
分析:在沒有寫入完成的時候,就開始了讀取,獲得的結果爲空
解決:
1.使用synchronized,雖然能夠解決,可是效率低下,寫操做同時不能讀,產生阻塞
2.使用讀寫鎖
public class TestWriteLock { Map<String, String> cache = new HashMap<>(); ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock(); ReentrantReadWriteLock.ReadLock readLock = lock.readLock(); //寫入元素 public void put(String key, String value) { try { writeLock.lock(); System.out.println("開始寫入key : " + key + " value : " + value); Thread.sleep(50); cache.put(key, value); System.out.println("完成寫入key : " + key + " value : " + value); } catch (Exception e) { e.printStackTrace(); } finally { writeLock.unlock(); } } //讀取元素 public String get(String key) { String value = ""; try { readLock.lock(); System.out.println("開始讀取key : " + key); value = cache.get(key); System.out.println("讀取成功key : " + key + " value : " + value); } catch (Exception e) { e.printStackTrace(); } finally { readLock.unlock(); } return value; } public static void main(String[] args) { TestWriteLock test = new TestWriteLock(); Thread readThread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { test.put("i", i + ""); } } }); Thread writeThread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { test.get("i"); } } }); readThread.start(); writeThread.start(); } }
觀察打印:完美解決
樂觀鎖:
簡單來說,樂觀鎖就是沒有鎖,無阻塞無等待
一條SQL語句作示範:
UPDATE TABLE SET X=X+1,VERSION=VERSION+1 WHERE ID=#{id} AND VERSION=#{version}
在高併發地狀況下,假設初始version是1,請求1到來,根據id和version能查到,因此容許更新
請求2同時作操做,可是根據id和version已經查不到了(被請求1修改了),因此不容許更新
悲觀鎖:
簡單來說,重量級鎖, 會阻塞,會進行等待
能夠理解爲上鎖以後只容許一個線程來操做,也就是Java中的synchronized
原子類:
一段模擬線程安全問題的代碼:
public class ThreadTest implements Runnable { private static int count = 1; @Override public void run() { while (true) { Integer count = getCount(); if (count >= 100) { break; } System.out.println(count); } } public synchronized Integer getCount() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } return count++; } public static void main(String[] args) { ThreadTest t = new ThreadTest(); new Thread(t).start(); new Thread(t).start(); } }
觀察打印後發現果真出現了線程安全問題
一種修改方式:效率較低
public synchronized Integer getCount() {
使用原子類:樂觀鎖,底層沒有加鎖,使用CAS無鎖技術
public class ThreadTest implements Runnable { // 線程安全 private AtomicInteger atomicInteger = new AtomicInteger(); @Override public void run() { while (true) { Integer count = getCount(); if (count >= 100) { break; } System.out.println(count); } } public Integer getCount() { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } return atomicInteger.incrementAndGet(); } public static void main(String[] args) { ThreadTest t = new ThreadTest(); new Thread(t).start(); new Thread(t).start(); } }
CAS無鎖技術(Compare And Swap):
翻譯過來爲:比較再交換
本地內存中存放共享內存的副本
好比主內存中有i=0,複製到兩個線程的本地內存中
兩個線程執行了i++,本地內存都變成i=1,而後刷新入主內存
CAS算法:
它包含三個參數CAS(V,E,N):
V表示要更新的變量(主內存)
E表示預期值(本地內存)
N表示新值(新值)
僅當V值等於E值時(主內存=本地內存),纔會將V的值設爲N
若是V值和E值不一樣(主內存!=本地內存),則說明已經有其餘線程作了更新,則當前線程什麼都不作
最後,CAS返回當前V的真實值。
觀察原子類的源碼:
/** * Atomically increments by one the current value. * * @return the updated value */ public final int incrementAndGet() { for (;;) { //獲取當前值 int current = get(); //設置指望值 int next = current + 1; //調用Native方法compareAndSet,執行CAS操做 if (compareAndSet(current, next)) //成功後纔會返回指望值,不然無線循環 return next; } }
CAS無鎖機制的缺點:
1.死循環
2.ABA問題:若是變量V初次讀取的時候是A,而且在準備賦值的時候檢查到它仍然是A,那能說明它的值沒有被其餘線程修改過了嗎
(若是在這段期間曾經被改爲B,而後又改回A,那CAS操做就會誤認爲它歷來沒有被修改過。針對這種狀況,java併發包中提供了一個帶有標記的原子引用類AtomicStampedReference,它能夠經過控制變量值的版原本保證CAS的正確性。)
JVM數據同步:採用分佈式鎖
自旋鎖和互斥鎖的區別:
悲觀和樂觀鎖的區別,自旋鎖是死循環不會阻塞,互斥鎖是同一時間只有一個線程訪問數據