天天看點

線程同步,同步鎖,重入鎖、條件對象

(借鑒自劉望舒的Android進階之光)

這個很好了解,有些資源是不安全的,如果多個線程同時通路會引起結果的錯誤,是以我們需要給他加一個同步鎖。即synchronized關鍵字,沒有人不熟悉他的,但是還有一個重入鎖就比較冷門了。

Lock l = new ReentrantLock();
l.lock();
try {
    ...
} finally {
    l.unlock();
}      

這個鎖保證了同一時刻,這一個代碼塊(稱之為臨界區)隻有一個線程可以進入。這裡finally中解鎖是很有必要的,否則如果一旦發生異常,另外所有的線程都阻塞住了。

可是當進入臨界區的時候,發現隻有子安一個條件滿足之後,這個線程才能執行,又該怎麼辦。這個時候就可以用一個條件對象(别名條件變量)來管理那些已經獲得了一個鎖但是卻不能做有用工作的線程。

一個例子說明條件對象的重要性,比如大家都在轉賬,這個操作必須是同步的吧。但是一旦他錢不夠了怎麼轉給别人?這個時候難道另外的線程跟着一起死鎖?是以需要把這個線程轉入這個條件的等待集并置為阻塞狀态,知道另一個線程調用了同一個條件的signalAll()(這是把這個條件的所有阻塞線程都開啟,并且讓他們在外面排隊,重新競争)為止。

public void transfer(int from, int to, int amount) throws InterruptedException{
    mLock.lock();
    try {
        while (accounts[from] < amount) {
            condition.await();
        }
        accounts[from] = accounts[from] - amount;
        accounts[to] = accounts[to] + amount;
        condition.signalAll();
    } finally {
        mLock.unlock();
    }
}      

while就是當錢不夠的時候,把這個線程讓條件對象來管理。下面的condition.signalAll();就是把條件對象管理的線程全部都激活,讓他們重新排隊。如果沒有線程可以進入這個臨界區,那就死鎖了。

全部代碼

class pay {
    private double[] accounts;
    private Lock mLock;
    private Condition condition;

    public pay(int n, double money) {
        accounts = new double[n];
        mLock = new ReentrantLock();
        condition = mLock.newCondition();
        for (int i = 0; i < accounts.length; i ++) {
            accounts[i] = money;
        }
    }

    public void transfer(int from, int to, int amount) throws InterruptedException{
        mLock.lock();
        try {
            while (accounts[from] < amount) {
                condition.await();
            }
            accounts[from] = accounts[from] - amount;
            accounts[to] = accounts[to] + amount;
            condition.signalAll();
        } finally {
            mLock.unlock();
        }
    }
}      

不過用synchronized鎖住一個方法是更友善的選擇

public synchronized void method(){
    ...
}      

用synchronized關鍵字來改寫上面的transfer方法

public synchronized void transfer(int from, int to, int amount) throws InterruptedException {
    while (accounts[from] < amount) {
        wait();
    }
    accounts[from] = accounts[from] - amount;
    accounts[to] = accounts[to] + amount;
    notifyAll();
}      

簡潔很多。使用synchronized,你必須了解到每一個對象有一個内部所,并且這個鎖有一個内部條件。wait().notifyAll()用法和之前的類似。

synchronized也可以通過使用同步代碼塊來獲得這個鎖。

synchronized(obj) {
    
}      

再用這個方法改寫一下

public synchronized void transfer(int from, int to, int amount) throws InterruptedException {
    synchronized((new Object())) {
        accounts[from] = accounts[from] - amount;
        accounts[to] = accounts[to] + amount;
    }
}      

這種方法不推薦用,因為僅僅隻能鎖住括号裡的對象,代碼塊是鎖不了的!一般同步實作最好用comcurrent并發包下的類。