線程學習之鎖,ReentrantLock,Synchronize

 

一.ReentrantLock特點:

 1.1 是獨佔鎖並且是可重入的:

獨佔互斥的,需要手動釋放鎖;

可重入的,但是要釋放相同次數的鎖;

1.2 默認是非公平鎖,也可以實現公平鎖:

 非公平鎖:當前線程直接嘗試獲取鎖,不管自己是不是身處隊尾;

公平鎖:按照隊列順序來,前面還有就等待;

創建的時候,加上參數true是公平鎖,公平鎖能夠避免線程飢餓

1.3 可以獲取鎖時限等待:(用來輔助解決死鎖):

在規定時間內獲取不到鎖,tryLock(),返回true false;

1.4 可以中斷線程lockInterruptibly():

1.5 condition:

condition 的使用代替了 wait  和 notify, 並且是在lock()方法調用之後使用,cndition signal()喚醒其他線程;

實現隊列 :

 private Lock lock = new ReentrantLock();
    private Condition  incon = lock.newCondition(); //進隊列
    private Condition  outcon = lock.newCondition(); //出隊列
    private  LinkedList<Integer> list = new LinkedList<Integer>();
    private  int  size=100;

    public void inLock(int i){
        try {
                lock.lock();
               while (list.size() == size)
                incon.await(); //等待
                list.add(i);
            System.out.println("入隊 "+i);
            outcon.signal(); //喚醒

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void outLock(){
        try {
            lock.lock();
            while (list.size() == 0)
                outcon.await(); //等待
            int e= list.removeFirst();
            System.out.println("出隊列 "+e);
            outcon.signal(); //喚醒

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}

1.6 公平鎖 非公平鎖的介紹:

流程 是嘗試獲取鎖,獲取失敗則封裝成節點放入對象中;

而獲取鎖的具體過程是

公平鎖;

先看下state =0,如果=,則說明是當前鎖沒被持有,並且判斷是否有其他線程比當前線程在同步隊列中等待的時間更長,後通過CAS操作獲取同步狀態,就獲取鎖;

否則檢測下當前線程是否是等於鎖線程,因爲是可重入鎖,所以會state++;等釋放的時候在減去,一直減到0;

否則會添加到隊列中;(在添加到雙向隊列中)

非公平鎖:

先看下state =0,如果=,當前線程會直接先嚐試修改state的狀態,如果修改成功直接獲得鎖,如果修改不成功就還是按照公平鎖來;

compareAndSetState 方法搶佔式加鎖,加鎖成功則將自己設爲持鎖線程,並返回;
若加鎖失敗,則調用 acquire 方法,將線程置於同步隊列尾部進行等待;
線程在同步隊列中成功獲取鎖,則將自己設爲持鎖線程後返回;
若同步狀態不爲0,且當前線程爲持鎖線程,則執行重入邏輯;

1.7 AQS:reentrantLock基於aQs原理

AQS是基於鏈表的雙向隊列,頭結點是當前獲取鎖的線程,對於所有沒有獲取鎖的都排在隊列中標,當頭結點運行完成後,會從隊列中刪除;

 

2.Synchronize

synchronize 底層   任何一個對象都有一個monitor相關聯,當montior被線程持有之後,對象就處於鎖定狀態,(存儲在java對象頭裏的是);

原理:  jvm 通過進入和退出對象監視器實現(montior)來實現方法 、同步塊的同步。

具體是在調用同步塊之前插入一個montiorenter指令,退出的時候插入一個montiorexit指令。保證同一時刻只能有一個線程獲取,其他線程被阻塞等待。

 

 

同步代碼塊:

當線程執行到monitorenter指令時,將會嘗試獲取對象所對應的monitor所有權,即嘗試獲取對象的鎖;monitorenter指令插入到同步代碼塊的開始位置,monitorexit指令插入到同步代碼塊的結束位置。

同步方法:

當執行方法時,會檢測Acc_SYNCHORNIZE 是否被設置過,如果設置了,就先進行獲取montior,執行完之後釋放montior,在執行期間其他線程不能夠在獲取。

對象在內存中的佈局分爲三塊區域:對象頭、對象實際數據和填充數據;

對象頭:

對象頭裏面放的是:  類型指針(用來確定實例是屬於哪個類) 和 標記字段;

標記字段放的是該實例運行時的一些信息,如哈希碼,gc年齡代,  monitor引用指針等;

 monitor:是線程私有的數據結構,線程有一個monitor record表,每一個被鎖住的對象都與一個montior相關聯,

同時,montior中有一個ower字段,存放的是與持有鎖的線程的唯一標誌。

鎖優化:

偏向鎖: 

在大多數的情況,往往是不存在競爭的,並且很可能是同一線程連續多次獲取這個鎖,爲了降低獲得鎖的代價,出現了偏向鎖。

當該鎖第一次被線程訪問的時候, 線程訪問該鎖並獲取鎖,在對象頭和棧幀中的鎖記錄裏存儲該線程的id。當該現場再次訪問的時候,只需要簡單的測試西夏,看對象頭markword中存在的是否是該線程id,如果是則說明該線程已經獲取到了該鎖,不必在去通過cas比較來獲得鎖。 如果測試失敗,則先去對象頭中查看是否存在偏向鎖的標誌符=1,如果存在說明是偏向鎖,則進行cas競爭嘗試讓markword中指向當前線程的id,如果不是則進行cas競爭獲取鎖。偏向鎖轉向輕量級鎖。

核心原理:      如果一個線程獲得了鎖,那麼鎖就進入偏向模式,此時Mark Word 的結構也變爲偏向鎖結構,當這個線程再次請求鎖時,無需再做任何同步操作,即獲取鎖的過程,這樣就省去了大量有關鎖申請的操作,從而也就提供程序的性能。

適用於:          沒有鎖競爭的場合,偏向鎖有很好的優化效果,畢竟極有可能連續多次是同一個線程申請相同的鎖。但是對於鎖競爭比較激烈的場合,偏向鎖就失效了。

 

 

輕量級鎖:

使用的是: 對絕大部分的鎖,在整個同步週期內都不存在競爭”有訪問鎖,適用於不存在競爭的情況

當關閉偏向鎖 或者 當多個競爭存在,導致偏向鎖升級爲輕量級鎖。

具體

1.獲取鎖 。

第一步:判斷是否處於無鎖狀態。如果是,則在當前線程的棧幀中創建一個lock record鎖記錄,將對象頭中的markword存儲到線程棧幀的鎖記錄中。 如果不是轉向第三步。

第二步: 通過cas嘗試將對象頭中的markword指向線程棧幀中的鎖記錄表,如果成功表示當前線程獲取鎖,將鎖標誌位變成00。如果失敗,嘗試自旋鎖獲取,失敗,轉向第三步。

第三步:判斷當前rmarkword指向的是否是當前線程的鎖記錄表,如果是,則表示當前線程已經獲取鎖。如果不是,存在競爭則轉向重量級鎖。

2. 釋放鎖

  取出在lock record中的保存的markword數據,嘗試cas替換當前對象的markword,成功表示釋放鎖了。失敗,則表示存在鎖競爭,膨脹成重量級鎖。

 

 

重量級鎖:

理念: 對絕大部分的鎖,在整個同步週期內都不存在競爭”

適用場景:   線程交替執行同步塊的場合,如果存在同一時間訪問同一鎖的場合,就會導致輕量級鎖膨脹爲重量級鎖。

 

鎖粗化:

將多次連續的加鎖/解鎖操作,換成一個更大範圍的鎖操作。

鎖消除:

對於些不必要的鎖操作,撤銷,節省無意義的鎖操作消耗時間。

自旋鎖:

當獲取鎖失敗的時候,不會立刻掛起,而是持續一段時間進行嘗試獲取。看當前鎖是不是可以在短時間內就釋放了。

自適應自旋鎖:

是爲了解決自旋鎖中自旋的次數問題,讓jvm更加的智能。自旋的次數不固定,線程自旋成功了,下次這個鎖自旋的次數增多;自旋失敗了,成功的次數很少,那麼這個鎖對應的下次自旋的次數減少。

樂觀鎖悲觀鎖:

樂觀鎖:   樂觀的認爲在拿數據的時候,當前的數據沒有其他線程進行修改,不加鎖,當前線程在更新數據的時候,需要判斷下當前數據是否被修改。

方式:

通過版本控制號 或者cas;

版本控制號:給數據設置個version版本控制號, 初始變量=0,線程在準備修改數據的時候,同時也會讀取這個數字, 提交更新時, 會對比下現在數據的version和開始時讀取的version, 二者一致纔會進行提交version+1,否則重試,知道更新成功。

 CAS:

在準備修改數據的時候,會通過cas進行比較,cas有三個值,一個是預期的現存進行比較的值A,一個是將要寫入的值B,一個是需要讀寫的內存上的值V,如果內存地址上的值與預期的值相等,則修改值。

使用場景 :讀操作比較多,而很少發生寫操作競爭的時候。

缺點: cas 的ABA問題,如果其他線程佔用時間過長,當前新線程自旋一直等待,佔用cpu,循環時間長開銷大。只能保證一個變量的原子性操作;a=a+b;無法保證

優點:使用與衝突比較少的情況,多讀操作,這樣的話可以減少鎖的開銷,提高吞吐量。

悲觀鎖: 悲觀的認爲每次取數據的時候,會有其他線程修改數據,所以先加上鎖,保證自己在修改讀取數據的時候,其他線程無法進行操作。同一時刻只能有一個線程進行操作。

向retrantLock synchroncized 都是悲觀鎖的實現。

 

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