多線程的同步

來源:互聯網
上載者:User

標籤:它的   控制   就是   寫鎖   法則   十分   成功   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 就無法保證操作的原子性,這個時候就可以用鎖。

多線程的同步

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.