code

2016年12月2日 星期五

Java concurrency 3 - intrinsic lock continued

Lock的持有與釋放

當一個lock被某個thread要求持有時,JVM會增值這個lock的計數器,當這個thread釋放這個lock的時候,計數器會減值。


JVM對lock的持有與釋放是以thread為請求單位,而非呼叫者(例如unix),這個設計考量是因為OOP裡面通常subclass要呼叫super class的method來做initialization,如果是以呼叫者來當作請求單位的話,就會deadlock。

public class Widget {
    public synchronized void doSomething() {
        
    }
}
public class LoggingWidget extends Widget {
    public synchronized void doSomething() {
        System.out.println(toString() + ": calling doSomething");
        super.doSomething();
    }
}


一個class要保護哪些variables?

只要有被多執行緒存取(不止write,也包括read) 到的可能,都應該要被lock機制保護。我們很容易在啟動timer的程式中忘了要做synchronization保護,事實上這就是另一個thread嘗試要存取shared data。

另外如果一個應該是atomic operation的block,其中相關的shared variables都要受到保護,這個我們已經在上一篇文章中有範例說明。注意的是一個operation即便所有方法都是lock保護,這整個operation也不是thread safe,要將整個operation block保護才能說是thread-safe:

if (!vector.contains(element))
        vector.add(element);

vector的所有methods都是synchronized methods,但是在執行contains和add之間仍有可能有多執行緒造成的不同順序結果,不是thread-safe。

不必要的synchronization

我們之前說的白做工的SynchronizedFactorizer由於把最關鍵的service method宣告為synchronized,使得所有thread都要等待上一個thread完整執行整個block後才能進入,這會造成嚴重的效能問題,特別是這是一個web service!!

我們可以縮小lock保護的範圍:

@ThreadSafepublic class CachedFactorizer implements Servlet {
    @GuardedBy("this") private BigInteger lastNumber;
    @GuardedBy("this") private BigInteger[] lastFactors;
    @GuardedBy("this") private long hits;
    @GuardedBy("this") private long cacheHits;
    public synchronized long getHits() { return hits; }
    public synchronized double getCacheHitRatio() {
        return (double) cacheHits / (double) hits;
    }
    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = null;
        synchronized (this) {
            ++hits;
            if (i.equals(lastNumber)) {
                ++cacheHits;
                factors = lastFactors.clone();
            }
        }
        if (factors == null) {
            factors = factor(i);
            synchronized (this)  {
                lastNumber = i;
                lastFactors = factors.clone();
            }
        }
        encodeIntoResponse(resp, factors);
    }
}

不過synchronization有overhead,所以不要分拆過細,只要包覆著atomic operation即可。

耗時間的block不要synchronized!

沒有留言:

張貼留言