標籤:它的 控制 就是 寫鎖 法則 十分 成功 ati 共用資料
在Java中,有四種方式來實現同步互斥訪問:synchronized 、 Lock 、wait() / notify() / notifyAll() 方法和 CAS(硬體CPU同步原語)。
一、synchronized
1. 同步代碼塊
1 synchronized(object){2 }
表示線程在執行的時候會將object 對象上鎖。(注意這個對象可以是任意類的對象,也可以使用this 關鍵字表示本對象或者是class 對象)。
可能一個方法中只有幾行代碼會涉及到線程同步問題,所以synchronized塊比synchronized 方法更加細粒度地控制了多個線程的訪問,只有synchronized 塊中的內容不能同時被多個線程所訪問,方法中的其他語句仍然可以同時被多個線程所訪問(包括synchronized 塊之前的和之後的)。
2. 作用於非靜態方法
1 public synchronized void increase(){2 i++;3 }
當一個線程訪問某個對象的synchronized 方法時,將該對象上鎖,其他任何線程都無法再去訪問該對象的synchronized 方法了,直到之前的那個線程執行方法完畢後(或者是拋出了異常),才將該對象的鎖釋放掉,其他線程才有可能再去訪問該對象的 synchronized 方法。
3. 作用於靜態方法
1 static int i=0;2 public synchronized void increase(){3 i++;4 }
當一個synchronized 關鍵字修飾的方法同時又被static 修飾,之前說過,非靜態同步方法會將對象上鎖,但是靜態方法不屬於對象,而是屬於類,它會將這個方法所在的類的Class 對象上鎖。
二、Lock 的用法。
java.util.concurrent.locks包中常用的類和介面:
Lock 介面及其實作類別 ReentrantLock
ReadWriteLock 介面 及其實作類別 ReentrantReadWriteLock
1.Lock & ReentrantLock
Lock lock = new ReentrantLock();lock.lock();try{ //處理任務}catch(Exception ex){ }finally{ lock.unlock(); //釋放鎖}
ReentrantLock是唯一實現了Lock介面的類。使用Lock 必須在 try-catch-finally 塊中進行,並且將釋放鎖的操作放在 finally 塊中進行,以保證鎖一定被釋放,防止死結的發生。lock 必須要手動釋放鎖,中斷不會自動釋放,synchronized 中斷後會自動釋放鎖。
2.ReadWriteLock & ReentrantReadWriteLock
1 public interface ReadWriteLock { 2 /** 3 * return the lock used for reading. 4 */ 5 Lock readLock(); 6 7 /** 8 * return the lock used for writing. 9 */10 Lock writeLock();11 }
讀鎖是共用鎖定,寫鎖是獨佔鎖定。如果有一個線程已經佔用了讀鎖,則此時其他線程如果要申請寫鎖,則申請寫鎖的線程會一直等待釋放讀鎖。如果有一個線程已經佔用了寫鎖,則此時其他線程如果申請寫鎖或者讀鎖,則申請的線程會一直等待釋放寫鎖。
三、wait() / notify() / notifyAll() 方法的使用(Java 中怎樣喚醒一個阻塞的線程?)
在Java 發展史上曾經使用suspend()、resume()方法對於線程進行阻塞喚醒,但隨之出現很多問題,比較典型的還是死結問題。
解決方案可以使用以對象為目標的阻塞,即利用Object 類的wait()和notify()方法實現線程阻塞。
首先,wait、notify 方法是針對對象的,調用任意對象的wait()方法都將導致線程阻塞,阻塞的同時也將釋放該對象的鎖,相應地,調用任意對象的notify()方法則將隨機解除該對象阻塞的線程,但它需要重新擷取改對象的鎖,直到擷取成功才能往下執行;其 次,wait、notify 方法必須在synchronized 塊或方法中被調用,並且要保證同步塊或方法的鎖對象與調用wait、notify 方法的對象是同一個,如此一來在調用wait 之前當前線程就已經成功擷取某對象的鎖,執行wait 阻塞後當前線程就將之前擷取的對象鎖釋放。
四、CAS(硬體CPU同步原語)
1. 如果不用鎖機制如何?共用資料訪問?
無鎖化編程的常用方法:硬體CPU 同步原語CAS(Compare and Swap)。
CAS是一種非阻塞的同步方式。CAS 實現了區別於sychronized 同步鎖的一種樂觀鎖,當多個線程嘗試使用CAS 同時更新同一個變數時,只有其中一個線程能更新變數的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知這次競爭中失敗,並可以再次嘗試。CAS 有3 個運算元,記憶體值V,舊的預期值A,要修改後的新值B。若且唯若預期值A 和記憶體值V 相同時,將記憶體值V 修改為B,否則什麼都不做。
一個線程間共用的變數,首先在主存中會保留一份,然後每個線程的工作記憶體也會保留一份副本。這裡說的預期值,就是線程保留的副本。當該線程從主存中擷取該變數的值後,主存中該變數可能已經被其他線程重新整理了,但是該線程工作記憶體中該變數卻還是原來的值,這就是所謂的預期值了。當你要用CAS重新整理該值的時候,如果發現線程工作記憶體和主存中不一致了,就會失敗,如果一致,就可以更新成功。
1 public final int incrementAndGet() {2 for (;;) {3 int current = get();4 int next = current + 1;5 if (compareAndSet(current, next))6 return next;7 }8 }
在這裡採用了CAS 操作,每次從記憶體中讀取資料然後將此資料和+1 後的結果進行CAS 操作,如果成功就返回結果,否則重試直到成功為止。
首先假設有一個變數i,i 的初始值為0。每個線程都對i 進行+1 操作。CAS是這樣保證同步的:假設有兩個線程,線程1 讀取記憶體中的值為0,current = 0,next = 1,然後掛起,然後線程2 對i 進行操作,將i 的值變成了1。線程2 執行完,回到線程1,進入if 裡的compareAndSet 方法,該方法進行的操作的邏輯是,(1)如果運算元的值在記憶體中沒有被修改,返回true,然後compareAndSet 方法返回next 的值(2)如果運算元的值在記憶體中被修改了,則返回false,重新進入下一次迴圈,重新得到current 的值為1,next 的值為2,然後再比較,由於這次沒有被修改,所以直接返回2。
2. CAS 的優缺點:
優點:CAS 由於是在硬體層面保證的原子性,不會鎖住當前線程,它的效率是很高的。
缺點:
1、迴圈時間長開銷大。自旋CAS 如果長時間不成功,會給CPU 帶來非常大的。執行開銷。因此CAS 不適合競爭十分頻繁的情境。
2、只能保證一個共用變數的原子操作。當對一個共用變數執行操作時,我們可以使用迴圈CAS 的方式來保證原子操作,但是對多個共用變數操作時,迴圈CAS 就無法保證操作的原子性,這個時候就可以用鎖。
多線程的同步