多線程—synchronized及同步器

synchronized關鍵字

synchronized有兩種類型的鎖:

  • 類鎖:synchronized修飾靜態方法、修飾一個類的class對象。
  • 對象鎖:除了類鎖,所有其他的上鎖方式都認爲是對象鎖。synchronized修飾普通方法、synchronized(this)給代碼塊上鎖等

規則:

  • 加了相同鎖的東西,任一時刻只能有一個線程持有鎖,其他線程等待。
  • 加了不同鎖的東西訪問互相不干擾 。

判斷:

  • 不同類型的鎖不是同一把鎖。
  • 加的是對象鎖,那麼必須是同一個對象實例纔是同一把鎖 。
  • 加的是類鎖,那必須是同一類纔是同一把鎖。

volatile關鍵字

所有線程的共享變量都存儲在主存(既內存)中,每一個線程都有一個獨有的工作內存,每個線程不直接操作在主內存中的變量,而是將主內存上變量的副本放進自己的工作內存中,只操作工作內存中的數據。當修改完畢後,再把修改後的結果放回到主內存中。這就導致多線程的環境下可能會出現髒數據,加上volatile關鍵字修飾的話,它可以保證當線程對變量值做了變動之後,會立即刷回到主內存中,而其它線程讀取到該變量的值也作廢,強迫重新從主內存中讀取該變量的值,這樣在任何時刻,線程總是會看到變量的同一個值。

線程阻塞與喚醒方法:

    1. sleep() 方法     不釋放鎖

  sleep(毫秒),指定以毫秒爲單位的時間,使線程在該時間內進入線程阻塞狀態,期間得不到cpu的時間片,等到時間過去了,線程重新進入可執行狀態。不會釋放資源。(暫停線程,不會釋放鎖)

  2.suspend() 和 resume() 方法

  掛起和喚醒線程,suspend()使線程進入阻塞狀態,只有對應的resume()被調用的時候,線程纔會進入可執行狀態。(不建議用,容易發生死鎖)

  3. yield() 方法     不釋放鎖

  會使的線程放棄當前分得的cpu時間片,但此時線程任然處於可執行狀態,隨時可以再次分得cpu時間片。yield()方法只能使同優先級的線程有執行的機會。調用 yield()的效果等價於調度程序認爲該線程已執行了足夠的時間從而轉到另一個線程。(暫停當前正在執行的線程,並執行其他線程,且讓出的時間不可知)

  4.wait() 和 notify() 方法     底層調用了wait,釋放鎖

  兩個方法搭配使用,wait()使線程進入阻塞狀態,調用notify()時,線程進入可執行狀態。wait()內可加或不加參數,加參數時是以毫秒爲單位,當到了指定時間或調用notify()方法時,進入可執行狀態。(屬於Object類,而不屬於Thread類,wait()會先釋放鎖住的對象,然後再執行等待的動作。由於wait()所等待的對象必須先鎖住,因此,它只能用在同步化程序段或者同步化方法內,否則,會拋出異常IllegalMonitorStateException.)

  5.join()方法     釋放鎖

  也叫線程加入。是當前線程A調用另一個線程B的join()方法,當前線程轉A入阻塞狀態,直到線程B運行結束,線程A才由阻塞狀態轉爲可執行狀態。

  6.await()和signal()     釋放鎖

  ConditionObject方法,實現線程掛起和喚醒。

sleep和wait的區別有:

  1. 這兩個方法來自不同的類分別是Thread和Object
  2. 最主要是sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。
  3. wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep可以在任何地方使用
synchronized(x){
      x.notify()
     //或者wait()
   }

同步器(Synchronizer)

Semaphore:

可以控制同時訪問資源的線程個數

public Semaphore(int permits)       //permits線程數

void acquire() //從信號量獲取一個許可,如果無可用許可前將一直阻塞等待,

void acquire(int permits)  //獲取指定數目的許可,如果無可用許可前也將會一直阻塞等待

boolean tryAcquire()  //從信號量嘗試獲取一個許可,如果無可用許可,直接返回false,不會阻塞
boolean tryAcquire(int permits)
boolean tryAcquire(int permits, long timeout, TimeUnit unit)

void release()  //釋放一個許可,在finally中使用,注意:多次調用該方法,會使信號量的許可數增加,達到動態擴展的效果,如:初始permits爲1, 調用了兩次release,最大許可會改變爲2

int availablePermits()  //獲取當前信號量可用的許可

CountDownLatch:

使一個線程等待其他線程各自執行完畢後再執行。是通過一個計數器來實現的,計數器的初始值是線程的數量。每當一個線程執行完畢後,計數器的值就-1,當計數器的值爲0時,表示所有線程都執行完畢,然後在閉鎖上等待的線程就可以恢復工作了。

public CountDownLatch(int count) //參數count爲計數值

public void await() throws InterruptedException  //調用該方法的線程會進入阻塞狀態,直到count值爲0才繼續執行
 
//和await()類似,只不過等待一定的時間後count值還沒變爲0的話就會繼續執行
public boolean await(long timeout, TimeUnit unit) throws InterruptedException
 
public void countDown();  //將CountDownLatch對象count值(初始化時作爲參數傳入構造方法)減1

Exchanger:

一個線程在完成一定的事務後想與另一個線程交換數據,則第一個先拿出數據的線程會一直等待第二個線程,直到第二個線程拿着數據到來時才能彼此交換對應數據。

Exchanger<T> exchanger = new Exchanger<T>();
 
//等待另一個線程到達此交換點(除非當前線程被中斷),然後將給定的對象傳送給該線程,並接收該線程的對象,否則阻塞。
data = exchanger.exchange(T);
 
//等待另一個線程到達此交換點(除非當前線程被中斷或超出了指定的等待時間),然後將給定的對象傳送給該線程,並接收該線程的對象。
exchange(T t, long timeout, TimeUnit unit):

CyclicBarrier:

它允許線程集等待直至其中預定數目的線程到達某個狀態(這個狀態叫公共障柵(barrier)),然後可以選擇執行一個處理障柵的動作。適用場景:當多個線程都完成某操作,這些線程才能繼續執行時,或都完成了某操作後才能執行指定任務時。對CyclicBarrier對象調用await方法即可讓相應線程進入barrier狀態,等到預定數目的線程都進入了barrier狀態後,這些線程就可以繼續往下執行了

//構造函數,parties 是參與線程的個數,Runnable 參數,最後一個到達線程要做的任務。
CyclicBarrier(int parties)
CyclicBarrier(int parties, Runnable barrierAction)
 
//線程調用await()表示到達柵欄,Exception 表示柵欄已經被破壞,可能是其中一個線程await()時被中斷或者超時
int await() throws InterruptedException, BrokenBarrierException
int await(long timeout, TimeUnit unit) throws InterruptedException,BrokenBarrierException, TimeoutException

 

 

 

 

 

 

 

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