Lock鎖+CAS+與Synchronized比較

在大學時代接觸Synchronized後,做的實驗就一直用它處理線程安全問題。但是我們都知道它都是塊狀的粒度,粗大粗大的毛孔,噴着厚重的氣息,方法執行到同步塊,性能抖三抖,加在方法上,就鎖住了整個實例對象,靜態方法上,還鎖住了整個類,所有這個類的實例對象都不能避免。monitorenter和monitorexit監視器實現的同步,虎視眈眈地看着每一個躡手躡腳進來的線程。步步緊跟,直到執行完同步塊釋放鎖。
Lock()是一個接口,是Java中的更加輕量級,更加靈活的,甚至可以自定義的鎖,通過實現這個接口,重寫它裏面的 lock() ,lockInterruptibly() , tryLock() , tryLock(long time, TimeUnit unit) , unlock() , newCondition() 這幾個方法,可以實現自定義鎖。
源碼註釋解析:
* {@code Lock} implementations provide more extensive locking
 * operations than can be obtained using {@code synchronized} methods
 * and statements.  They allow more flexible structuring, may have
 * quite different properties, and may support multiple associated
 * {@link Condition} objects.
 * A lock is a tool for controlling access to a shared resource by
 * multiple threads. Commonly, a lock provides exclusive access to a
 * shared resource: only one thread at a time can acquire the lock and
 * all access to the shared resource requires that the lock be
 * acquired first. However, some locks may allow concurrent access to
 * a shared resource, such as the read lock of a {@link ReadWriteLock}.
 * The use of {@code synchronized} methods or statements provides
 * access to the implicit monitor lock associated with every object, but
 * forces all lock acquisition and release to occur in a block-structured way:
 * when multiple locks are acquired they must be released in the opposite
 * order, and all locks must be released in the same lexical scope in which
 * they were acquired.
 * While the scoping mechanism for {@code synchronized} methods
 * and statements makes it much easier to program with monitor locks,
 * and helps avoid many common programming errors involving locks,
 * there are occasions where you need to work with locks in a more
 * flexible way. For example, some algorithms for traversing
 * concurrently accessed data structures require the use of
 * "hand-over-hand" or "chain locking": you
 * acquire the lock of node A, then node B, then release A and acquire
 * C, then release B and acquire D and so on.  Implementations of the
 * {@code Lock} interface enable the use of such techniques by
 * allowing a lock to be acquired and released in different scopes,
 * and allowing multiple locks to be acquired and released in any
 * order.
 * With this increased flexibility comes additional
 * responsibility. The absence of block-structured locking removes the
 * automatic release of locks that occurs with {@code synchronized}
 * methods and statements. In most cases, the following idiom
 * should be used:
 * When locking and unlocking occur in different scopes, care must be
 * taken to ensure that all code that is executed while the lock is
 * held is protected by try-finally or try-catch to ensure that the
 * lock is released when necessary.
 *
 * <p>{@code Lock} implementations provide additional functionality
 * over the use of {@code synchronized} methods and statements by
 * providing a non-blocking attempt to acquire a lock ({@link
 * #tryLock()}), an attempt to acquire the lock that can be
 * interrupted ({@link #lockInterruptibly}, and an attempt to acquire
 * the lock that can timeout ({@link #tryLock(long, TimeUnit)}).

Lock接口提供了更具擴展性的鎖,允許更加靈活的結構,更多種類的屬性,且不使用synchronized關鍵字聲明和相關的方法。
Lock接口是一種對多線程共享資源訪問的控制工具。一次只能有一個線程獲取鎖,其餘線程訪問共享資源都會要求先獲得鎖,但是也會有一些鎖允許併發訪問資源的操作,比如讀鎖。
Synchronized方法或者聲明語句提供了針對每個對象的隱式監視器,但是獲取鎖和釋放鎖操作都是以塊狀粒度。當獲取鎖的時候都要求必須先執行與獲取鎖相反的釋放指令。而且全部的鎖都必須在執行完他們鎖的語義範圍之後釋放。
Synchronized方法的作用域機制,並且通過聲明語句使得實現監視器鎖編程更加簡單。
同時有利於避免許多關於鎖的常見編程錯誤。
但是有些時候你會需要在一個更加靈活的方式下使用鎖,例如一些遍歷算法,併發訪問的數據結構。比如:先獲取節點A的鎖,再獲取節點B的鎖,然後釋放A獲取C,釋放B獲取D,以此類推。
那麼這種靈活的鎖就可以通過Lock()來實現,在不同的邏輯範圍內通過不同的指令條件來獲取和釋放鎖,
隨着出現越來越多的這種對靈活性要求的場景,無用的塊狀結構鎖將被淘汰刪除。自動釋放和加鎖都應該採用方法聲明,在更多情況下,Lock()應該使用:
Lock() l = (你自定義的實現了Lock接口的鎖) …;
l.lock() ; 加鎖
try(){
} finally {
l.unlock(); 釋放鎖
}
當鎖定和解鎖在不同的範圍發生時,必須確保用try catch捕抓鎖定的代碼塊執行時候出現的異常,確保必要時釋放鎖不至於造成死鎖。
Lock() 提供了新的方法 tryLock() 是嘗試獲取鎖,以及tryLock(long,TimeUnit)來設置鎖的固定超時時間。
Lock() 接口的實現類提供的行爲和語義都與隱式監視器鎖有很大的不同,例如可以保證排序,不可重入或者死鎖檢測。
注意Lock()實例只是普通對象,實例的鎖監視器鎖與Lock實例沒有特別的關係,除了在它們自己的實現中,建議不要在它們自己的實例中使用Lock這樣的實例來避免混淆。
除非另有說明,否則傳遞的任何參數都要拋出空指針異常(這也就是必須用try catch 捕獲的原因之一)。

大概的介紹噼裏啪啦就是這麼一個意思,對比了一下Lock接口和Synchronized聲明式鎖的區別。

在這裏插入圖片描述

關於它的這六個方法,註解也介紹的非常詳細了,這裏只看最常用的加鎖和釋放鎖方法。

void lock();

在這裏插入圖片描述
如果對於當前線程來說暫時未能獲取到鎖,則會進入休眠狀態直到獲取鎖。使用lock的實現可能會檢測到錯誤的使用導致死鎖,所以這種情況下要聲明異常類型進行捕獲並且釋放鎖。

void unlock();

在這裏插入圖片描述
釋放鎖,通常只有獲取鎖的線程在順利執行完之後釋放,也可能拋出異常後釋放,或者超出了時間限制時候釋放等等,並且異常類型必須有Lock的實現類來記錄處理。

反正它就是反覆強調防止死鎖記住一定一定要釋放,要加異常捕獲機制,要加超時釋放等等。

Lock()中有公平鎖和非公平鎖概念,公平鎖就是順序加鎖,根據線程的排隊先後順序獲取,非公平鎖就是大家瘋搶,誰搶到就是誰的。

Lock lock1 = new ReentrantLock();默認是非公平鎖
Lock lock2 = new ReentrantLock(true);公平鎖。

具體用法:

由於它是一個接口,那就用它一個實現類ReentrantLock()寫個例子來觀摩一下。

在這裏插入圖片描述

順利出現線程不安全問題。

下面開始加Lock().

public class LockDemo {

    private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
            new Thread(new ThreadDemo(),"A").start();
            new Thread(new ThreadDemo(),"B").start();
            new Thread(new ThreadDemo(),"C").start();
    }

    public static class ThreadDemo implements Runnable {
        private static int a =500;
        @Override
        public void run() {

            while(true){
                    lock.lock();
                    if(a>0){
                        System.out.println(Thread.currentThread().getName()+" say :"+ a--);
                    }
                    lock.unlock();
            }
            }
    }
}

在這裏插入圖片描述
發現這樣就正常了,操作前獲取鎖,操作後釋放鎖給別的線程。

並不是所有情況下都是Synchronized不好,也不是所有情況下Lock()都好,要根據場景。
Lock()只是Java代碼實現的鎖,比如需要靈活多變的,非塊狀鎖的,條件判斷可中斷的,或者一些複雜多變的加鎖過程,設置重試時間的鎖,有序公平鎖等等場景才用Lock()。由於它的輕量級和擴展性和高性能,適合大量同步的場景。
而Synchronized作爲Java的老牌關鍵字,不需要我們加鎖解鎖和捕抓異常解鎖防止死鎖,使用上更加簡單明瞭,不容易出現差錯,而且自從JDK1.6之後,開發團隊不斷進行鎖優化,大大提升了性能,所以各有各的優點。大量同步的場景還是儘量避免Synchronized,代碼執行到這裏強制變爲單線程畢竟還是影響性能。
這麼牛逼的Lock();既然能實現線程安全,又避免使用Synchronized,是怎麼實現的?
繼續看ReentrantLock的底層源碼:

在這裏插入圖片描述

他有兩個方法,假如傳進來一個Boolean, true就是代表公平鎖,默認是false不公平鎖。我們繼續進入FairSync 和 NonfairSync看看。

在這裏插入圖片描述
在這裏插入圖片描述

發現它們兩個都是Sync的兒子。

在這裏插入圖片描述

Sync又是一個叫做AbstractQueuedSynchronizer(簡稱AQS)的兒子。那玩意點進去一看:

在這裏插入圖片描述
在這裏插入圖片描述

像不像鏈表結構??它底層還真的是Node節點結構!!AbstractQueuedSynchronizer顧名思義,其實就是抽象隊列同步。通過類似的鏈表結構讓多線程操作在這裏排隊,依次拿線程任務出去執行,當前面一個線程執行完畢之後,lock.unlock()之後,就激活後續節點任務執行。這個抽象隊列,名字叫做CLH queue 。

在這裏插入圖片描述

大略看一下,這個queue存的居然是Thread,還有nextWaiter下一個等待者線程。

在這裏插入圖片描述

循環地將新增的線程追加到隊尾,並返回Node。
還有一大堆的方法,有時間再仔細研究。

CAS:(Compare and Swap)

基於算法實現的一種樂觀鎖。是CPU指令級別的原子操作。
在這裏插入圖片描述

在這裏插入圖片描述

不管是設置前節點,後節點,還是等待狀態,都是使用三個操作參數,當前內存位置,預期值和改動後的新值。查詢內存位置V存放的是否A值,比較後如果是,則把更新後的B值放到V,如果不是,則直接返回當前這個內存位置的值。
具體詳情可以看這位大佬寫的:https://www.jianshu.com/p/ab2c8fce878b
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章