併發 · 二 —— 互斥鎖(synchronized 和 Lock)

在第一篇中,我整理了多線程的創建及調度多線程的方法,有興趣可以通過下面的鏈接過去瞧瞧。

https://blog.csdn.net/m0_37605407/article/details/86563800

      下面正文開始。

      在單線程中,由於代碼是順序運行的,同一個資源在某一個時刻只會被一個實體使用,並不會出現兩個實體同時使用同一個資源的情況。但是在多線程的情況,這種情況就變得常見了,同一個資源可能會被多個實體同時使用。此時就是出現資源不同步的情況,和一些意想不到的問題的出現。

      爲了避免這種情況出現,我們可以在讀寫數據的代碼前面加上一條鎖語句避免這種情況的,這就使得在一段時間內只有一個線程可以運行這塊代碼塊(即只有一個線程可以持有這塊代碼的鎖,其他線程必須等待直到鎖被釋放,才能進行競爭從而獲得鎖)。這種效果稱之爲互斥量(mutex)。 

一、使用synchronized(也叫內建鎖)進行修飾,主要分爲以下幾種場景

      1、修飾方法

synchronized void fun();

      注意:所有的對象都自動含有單一的鎖(也稱爲監視器),當A線程調用synchronized修飾的方法的這段時間內,其它線程調用該方法時,會被阻塞,直到A線程運行完該方法後,其他線程才能進入該方法。當有多個synchronized修飾的方法時,A線程調用其中一個synchronized的方法,其它線程在調用到其中一個synchronized修飾的方法時均會被阻塞,直至A線程運行完所調用的synchronized方法後,纔可以進去同樣是synchronized修飾的方法,只是因爲所有synchronized方法共享一個對象鎖,當然,以上說法都是針對同一個對象而言的。

      2、修飾代碼塊

synchronized{
/*代碼塊*/
}

       這種使用場景是在一個方法內只有少量的代碼塊需要加鎖的情況下,這種的使用效果和用synchronized修飾方法的效果一樣,可以僅將需要加鎖代碼塊進行加鎖,避免不必要的加鎖,這可以很大的提高併發的速度。

      這是另外一種使用形式:

synchronized(Object object){/*代碼塊*/}

     兩者的不同之處是,第一種使用形式是使用當前對象含有的鎖進行代碼加鎖,第二種使用形式是使用object對象含有的鎖進行加鎖。

      注意:同一個線程可以多次獲得對象鎖。JVM負責跟蹤對象被加鎖的次數,如果一個對象被解鎖(即是所有的鎖被釋放),其計數就會變爲0,其它線程就可以來競爭鎖。只有首先獲得鎖的時候,才允許繼續獲得鎖。

      3、修飾靜態方法

synchronized static void fun();

      針對每個類,也有一個鎖,所有synchronized static方法可以在類的範圍內防止對static數據的併發訪問。

二、使用java.util.concurrent類庫中Lock對象進行顯式加鎖

      與synchronized相比而言,Lock對象必須顯式的創建、加鎖和釋放。代碼會比較缺乏優雅,但是使用更加的靈活。

Lock lock = new ReentrantLock();
lock.lock();
try{
    /*同步代碼塊*/
}finally{
    lock.unlock();
}

      使用時必須要嵌套try-finally使用,因爲,必須保證不管同步代碼塊出現什麼問題,到最後都必須釋放Lock對象,避免死鎖的出現。

      還有,爲什麼說Lock更加靈活呢?因爲使用Lock對象進行加鎖,它可以嘗試獲取鎖且最終獲取鎖可以失敗,這是synchronized所體驗不到的操作。

Lock lock = new ReentrantLock();
if(lock.trylock()){
    // 獲取鎖成功
    try{
    /*同步代碼塊*/
    }finally{
        lock.unlock();
    }
}else{
   log.e(TAG,"獲取鎖失敗");
}

      這樣,當請求鎖時,如果鎖被其他線程所持有着,就可以選擇是否繼續等待,相對較synchronized修飾時,只能一直被阻塞,是不是就變得更加靈活了。

      此外,還能設置等待阻塞的時間(即等待鎖被釋放):

Lock lock = new ReentrantLock();
try{
    boolean isGetLock = lock.trylock(1,TimeUnit.SECONDS);
}catch(InterruptedException e){

}
if(isGetLock){
    try{
        /*同步代碼塊*/
    }finally{
        lock.unlock()
    }
}else{
    // 等待鎖超時,最終沒有能獲得鎖
}

      這個在代碼出現的InterruptedException異常,後面會開一篇出來說它(其實是太晚不想寫了,嘻嘻)。

      注意:使用Lock對象必須注意處理Lock對象的釋放,不然就會出現死鎖,所以在可以使用synchronized解決的情況下,就不要爲了玩騷的,去使用Lock對象,可以避免出現忘記釋放鎖,從而出現死鎖。而且使用synchronized更優雅不是嗎,哈哈哈。

      到這裏,這篇就結束了,總結來說,這篇整理的互斥鎖的基本使用。

 

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