java-多線程深入(六)鎖

java多線程中提供的鎖:synchronized和lock。

(一)synchronized

1、synchronized的使用

每個對象都自帶鎖,鎖可以同步實例方法(this是對象鎖)、靜態方法(class是對象鎖)、方法塊(synchronized參數是對象鎖

下面是鎖住實例方法:

public synchronized void add(){
        a++;
    }
使用注意點:

(1)Object的wait、notify和notifyAll使用時需在代碼外層加鎖,等待和喚醒鎖必須相同,使用的鎖不能發生改變,不然會拋出IllegalMonitorStateException異常

/**
 * 線程等待喚醒測試
 * 
 * @author peter_wang
 * @create-time 2014-10-9 下午2:50:36
 */
public class ThreadNotifyTest {
    private static Integer num = new Integer(0);

    /**
     * @param args
     */
    public static void main(String[] args) {
        final Thread thead1 = new Thread() {
            @Override
            public void run() {
                synchronized (num) {
                    try {
                        sleep(2000);
                        num.wait();
                        System.out.println("解鎖成功");
                    }
                    catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        thead1.start();

        Thread thead2 = new Thread() {
            @Override
            public void run() {
                try {
                    sleep(1000);
// num = new Integer(1);  //A
                    num++;//B
                }
                catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        thead2.start();
    }
}
無論執行A或B,改變了鎖num後,wait執行的時候會拋出IllegalMonitorStateException異常。
對wait、notify加鎖是爲了保證它們在執行中的原子性。

(2)使用的鎖儘量是不可變對象,使用private final Object對象,可變化的對象可能導致不可預知的後果,如wait的問題。

(3)synchronized鎖住區域儘量減少,提高性能。

2、synchronized原理探究

(1)線程狀態

當多個線程同時請求某個對象監視器時,對象監視器會設置幾種狀態用來區分請求的線程: 
Contention List:所有請求鎖的線程將被首先放置到該競爭隊列 
Entry List:Contention List中那些有資格成爲候選人的線程被移到Entry List,降低對Contention List的爭用

Wait Set:那些調用wait方法被阻塞的線程被放置到Wait Set 

OnDeck:任何時刻最多只能有一個線程正在競爭鎖,該線程稱爲OnDeck Owner:獲得鎖的線程稱爲Owner !Owner:釋放鎖的線程
(2)鎖類型

公平鎖和非公平鎖

公平鎖:每個線程取得調度的機率是一樣的

非公平鎖:每個線程取得的調度機率不同,是公平鎖吞吐率的5-10倍

自旋鎖和阻塞鎖

自旋鎖:線程阻塞調度過程設計到操作linux內核線程,需要在用戶態和內核態之間切換狀態,性能消耗比較大,自旋機制讓請求調度的線程內部自循環,不切換狀態等待一段時間,若仍然未能獲取調度機會再轉換鎖類型

阻塞鎖:阻塞鎖在線程競爭時,無獲取到調度的線程直接進入阻塞隊列

多種阻塞鎖類型

偏向鎖:在大多數情況下,鎖都存在於單線程中,爲了讓線程獲得鎖減少性能代價,同一線程多次重入,不會執行CAS操作,直到遇見多線程競爭,轉換成其他類型

輕量鎖:偏向鎖的升級版或者直接設置系統不使用偏向鎖直接進入輕量鎖,比偏向鎖多了步CAS操作,當前若鎖未被其他線程鎖住即可使用,操作失敗進入自旋鎖

重量鎖:完整的阻塞鎖狀態,對象監視器(Monitor),由自旋鎖超時升級而成

鎖的進化過程:偏向鎖—>輕量鎖—>自旋鎖—>重量鎖

(3)總結

synchronized執行時,優先使用偏向鎖或輕量鎖提升性能,碰到多線程鎖住現象,進入自旋狀態,等待未果進入重量鎖階段,阻塞線程,放入阻塞隊列,切換線程狀態。


(二)Lock

1、ReentrantLock的使用

private ReentrantLock mlock = new ReentrantLock();
@Override
    public void write() {
        mlock.lock();
        try {
            long startTime = System.currentTimeMillis();
            System.out.println("開始往這個buff寫入數據…");
            for (;;)// 模擬要處理很長時間
            {
                if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE)
                    break;
            }
            System.out.println("終於寫完了");
        }
        finally {
            mlock.unlock();
        }
    }
ReentrantLock在lock的時候鎖住實例對象,必須在finally中unlock解鎖,防止異常拋出未解鎖。

2、ReentrantLock原理分析

ReentrantLock中的操作都是基於Sync,Sync繼承自AbstractQueuedSynchronizer。

AbstractQueuedSynchronizer通過構造一個基於阻塞的CLH隊列容納所有的阻塞線程,而對該隊列的操作均通過Lock-Free(CAS)操作,但對已經獲得鎖的線程而言,ReentrantLock實現了偏向鎖的功能。


(三)synchronized和ReentrantLock對比

1、性能上synchronized是native方法性能優化較多,ReentrantLock是java層實現性能不一定很好。

2、ReentrantLock提供了更多功能,如公平鎖和非公平鎖設置等,但是需要使用finally解鎖。

3、在普通情況下使用synchronized,在業務複雜需要使用ReentrantLock特殊功能的才使用ReentrantLock。


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