併發編程(六)——java中鎖怎麼使用?

Java鎖的深度化

      當多個請求同時操作數據庫時,首先將訂單狀態改爲已支付,在金額加上200,在同時併發場景查詢條件下,會造成重複通知。
SQL:
Update

悲觀鎖與樂觀鎖

      悲觀鎖悲觀的認爲每一次操作都會造成更新丟失問題,在每次查詢時加上排他鎖。

      每次去拿數據的時候都認爲別人會修改,所以每次在拿數據的時候都會上鎖,這樣別人想拿這個數據就會block直到它拿到鎖。傳統的關係型數據庫裏邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。

Select * from xxx for update;

      樂觀鎖:樂觀鎖會樂觀的認爲每次查詢都不會造成更新丟失,利用版本字段控制

常用樂觀鎖框架

重入鎖

      鎖作爲併發共享數據,保證一致性的工具,在JAVA平臺有多種實現(如 synchronized 和 ReentrantLock等等 ) 。這些已經寫好提供的鎖爲我們開發提供了便利。

      重入鎖,也叫做遞歸鎖,指的是同一線程 外層函數獲得鎖之後 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。

      在JAVA環境下 ReentrantLock 和synchronized 都是 可重入鎖

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();
		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();
	}

}

CAS無鎖機制

      (1)與鎖相比,使用比較交換(下文簡稱CAS)會使程序看起來更加複雜一些。但由於其非阻塞性,它對死鎖問題天生免疫,並且,線程間的相互影響也遠遠比基於鎖的方式要小。更爲重要的是,使用無鎖的方式完全沒有鎖競爭帶來的系統開銷,也沒有線程間頻繁調度帶來的開銷,因此,它要比基於鎖的方式擁有更優越的性能。

      (2)無鎖的好處:
      第一,在高併發的情況下,它比有鎖的程序擁有更好的性能;
      第二,它天生就是死鎖免疫的。

      就憑藉這兩個優勢,就值得我們冒險嘗試使用無鎖的併發。

      (3)CAS算法的過程是這樣:它包含三個參數CAS(V,E,N): V表示要更新的變量,E表示預期值,N表示新值。僅當V值等於E值時,纔會將V的值設爲N,如果V值和E值不同,則說明已經有其他線程做了更新,則當前線程什麼都不做。最後,CAS返回當前V的真實值。

      (4)CAS操作是抱着樂觀的態度進行的,它總是認爲自己可以成功完成操作。當多個線程同時使用CAS操作一個變量時,只有一個會勝出,併成功更新,其餘均會失敗。失敗的線程不會被掛起,僅是被告知失敗,並且允許再次嘗試,當然也允許失敗的線程放棄操作。基於這樣的原理,CAS操作即使沒有鎖,也可以發現其他線程對當前線程的干擾,並進行恰當的處理。

      (5)簡單地說,CAS需要你額外給出一個期望值,也就是你認爲這個變量現在應該是什麼樣子的。如果變量不是你想象的那樣,那說明它已經被別人修改過了。你就重新讀取,再次嘗試修改就好了。

      (6)在硬件層面,大部分的現代處理器都已經支持原子化的CAS指令。在JDK 5.0以後,虛擬機便可以使用這個指令來實現併發操作和併發數據結構,並且,這種操作在虛擬機中可以說是無處不在。

/** 
	 * 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;  
	    }  
	}  

自旋鎖

      自旋鎖是採用讓當前線程不停地的在循環體內執行實現的,當循環的條件被其他線程改變時 才能進入臨界區。如下

	private AtomicReference<Thread> sign =new AtomicReference<>();
	public void lock() {
		Thread current = Thread.currentThread();
		while (!sign.compareAndSet(null, current)) {
          }
	}
	public void unlock() {
		Thread current = Thread.currentThread();
		sign.compareAndSet(current, null);
	}

public class Test implements Runnable {
	static int sum;
	private SpinLock lock;

	public Test(SpinLock lock) {
		this.lock = lock;
	}

	/**
	 * @param args
	 * @throws InterruptedException
	 */
	public static void main(String[] args) throws InterruptedException {
		SpinLock lock = new SpinLock();
		for (int i = 0; i < 100; i++) {
			Test test = new Test(lock);
			Thread t = new Thread(test);
			t.start();
		}

		Thread.currentThread().sleep(1000);
		System.out.println(sum);
	}

	@Override
	public void run() {
		this.lock.lock();
		this.lock.lock();
		sum++;
		this.lock.unlock();
		this.lock.unlock();
	}

}

      當一個線程 調用這個不可重入的自旋鎖去加鎖的時候沒問題,當再次調用lock()的時候,因爲自旋鎖的持有引用已經不爲空了,該線程對象會誤認爲是別人的線程持有了自旋鎖

      使用了CAS原子操作,lock函數將owner設置爲當前線程,並且預測原來的值爲空。unlock函數將owner設置爲null,並且預測值爲當前線程。

      當有第二個線程調用lock操作時由於owner值不爲空,導致循環一直被執行,直至第一個線程調用unlock函數將owner設置爲null,第二個線程才能進入臨界區。

      由於自旋鎖只是將當前線程不停地執行循環體,不進行線程狀態的改變,所以響應速度更快。但當線程數不停增加時,性能下降明顯,因爲每個線程都需要執行,佔用CPU時間。如果線程競爭不激烈,並且保持鎖的時間段。適合使用自旋鎖。

分佈式鎖

      如果想在不同的jvm中保證數據同步,使用分佈式鎖技術。

      有數據庫實現、緩存實現、

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章