線程安全篇B
為了保持狀態(tài)的一致性,需要在一個(gè)原子性操作中更新相關(guān)的狀態(tài)變量,加鎖,可以將一些混合操作變?yōu)樵有圆僮鳎瑥亩WC線程安全。
鎖
- 內(nèi)部鎖
Java提供了一個(gè)加強(qiáng)原子性的內(nèi)在鎖機(jī)理-同步塊,其包含兩部分,一部分是一個(gè)可以作為鎖的對象的引用,另一部分是由這個(gè)鎖保護(hù)的阻塞代碼。同步方法是同步塊的一個(gè)快捷體現(xiàn),其作用于所有的方法體。
每一個(gè)Java對象都可以作為一個(gè)潛在的同步鎖,這種內(nèi)部構(gòu)建的鎖叫做內(nèi)部鎖或是管程鎖。當(dāng)線程進(jìn)入同步塊前會(huì)自動(dòng)獲得鎖,并在執(zhí)行退出(正常或異常的情況)后,自動(dòng)釋放鎖。
內(nèi)在鎖是一種互斥鎖,即至多有一個(gè)線程可以擁有鎖。當(dāng)線程A想要獲得線程B獲得的鎖,它必須等待或者阻塞,直到B線程釋放鎖,若不釋放,A會(huì)一直等待。
- 重入性
重入性指的是鎖的獲得基于每個(gè)線程,而不是基于每次調(diào)用;一個(gè)線程可以成功請求獲得它已經(jīng)持有的鎖;
該特性的實(shí)現(xiàn)是因?yàn)殒i的請求獲得次數(shù)(lockcount)和持有它的線程的關(guān)聯(lián)來實(shí)現(xiàn)的。當(dāng)一個(gè)線程獲得或者再次獲得鎖時(shí),在虛擬機(jī)里將會(huì)lockcount+1,當(dāng)一個(gè)同步塊退出時(shí),虛擬機(jī)會(huì)讓lockcount-1。當(dāng)lockcount=0時(shí),該線程釋放鎖。
重入性簡化了面向?qū)ο蟮牟l(fā)操作,否則的話,像如下代碼中,將會(huì)發(fā)生死鎖。
public class Widget {
public synchronized void doSomething() {
...
}
}
public class LoggingWidget extends Widget {
public synchronized void doSomething() {
System.out.println(toString() + ": calling doSomething");
super.doSomething();
}
}
用鎖保護(hù)狀態(tài)
由于鎖可以將對代碼的訪問變得序列化,因此可以使用鎖來構(gòu)建一些策略來保障共享狀態(tài)的排他性。
一個(gè)可以被多個(gè)線程訪問的可變狀態(tài),所有的訪問都需要持有共同的鎖,此時(shí),我們稱這個(gè)狀態(tài)被這個(gè)鎖保護(hù)。
對象的內(nèi)部鎖和它的狀態(tài)沒有內(nèi)在的關(guān)聯(lián),一個(gè)對象的成員不會(huì)自動(dòng)被其內(nèi)部鎖保護(hù)。獲得一個(gè)對象的內(nèi)部鎖,僅僅會(huì)阻止其它線程獲得這個(gè)鎖,但并不妨礙其它線程訪問這個(gè)對象。
每次共享時(shí),一個(gè)可變狀態(tài)需要指定一個(gè)具體的鎖,并使其使用者知道是哪個(gè)鎖。
對于每一個(gè)擁有多于一個(gè)變量的不變量來說,它的所有變量應(yīng)該使用同一個(gè)鎖來保護(hù)。
一個(gè)通用的鎖策略是,將所有可變狀態(tài)用一個(gè)對象封裝起來,然后所有訪問這些可變狀態(tài)的代碼都用這個(gè)對象的內(nèi)部鎖保護(hù)起來,用來防止并發(fā)訪問。這種方式很常用,也很簡單,但是這樣會(huì)導(dǎo)致活性和性能問題。
活性和性能
不合適的同步,特別是同步耗時(shí)操作,會(huì)使得并發(fā)能力下降,甚至失去并發(fā)能力。
用兩段代碼解釋更直觀。
- 粗糙同步,省略了一些次要代碼。Servlet框架的設(shè)計(jì)本意是同時(shí)處理多個(gè)請求,但是同步后的service方法一旦有耗時(shí)操作,那么由于同步的原因,其它的請求只能等上一個(gè)請求完成之后才能進(jìn)行,降低了處理性能。
public class SynchronizedFactorizer extends GenericServlet implements Servlet {
@GuardedBy("this") private BigInteger lastNumber;
@GuardedBy("this") private BigInteger[] lastFactors;
public synchronized void service(ServletRequest req,
ServletResponse resp) {
BigInteger i = extractFromRequest(req);
if (i.equals(lastNumber))
encodeIntoResponse(resp, lastFactors);
else {
BigInteger[] factors = factor(i);
lastNumber = i;
lastFactors = factors;
encodeIntoResponse(resp, factors);
}
}
void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) {
}
BigInteger extractFromRequest(ServletRequest req) {
return new BigInteger("7");
}
BigInteger[] factor(BigInteger i) {
// Doesn't really factor
return new BigInteger[] { i };
}
}
- 精細(xì)同步,只需進(jìn)行精致的分割,改變同步的位置,便能盡可能的消除加鎖對性能帶來的影響。見代碼:
public class CachedFactorizer extends GenericServlet 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);
}
void encodeIntoResponse(ServletResponse resp, BigInteger[] factors) {
}
BigInteger extractFromRequest(ServletRequest req) {
return new BigInteger("7");
}
BigInteger[] factor(BigInteger i) {
// Doesn't really factor
return new BigInteger[]{i};
}
}
如何確定同步塊的大小需要考慮安全性,易用性和性能。有時(shí)候,易用性和性能是一對相對矛盾的設(shè)計(jì)指標(biāo)。無論何時(shí)在使用鎖時(shí),應(yīng)該考慮到被同步的代碼能做什么,以及這些代碼耗時(shí)大概多少。進(jìn)行大量的計(jì)算或是存在潛在的阻塞操作,都有可能加長持有鎖的時(shí)間,進(jìn)一步影響到程序的活性和性能。
涉及大量計(jì)算和耗時(shí)操作(如net或者io)的代碼,應(yīng)避免持有鎖。
//待下篇