第二章-線程安全性

  • 線程安全核心:
對(duì)狀態(tài)訪問的控制,特別是共享(shared)和可變(Mutable)狀態(tài)的訪問
  • 共享:
由多個(gè)線程同時(shí)訪問
  • 可變:
在變量的生命周期內(nèi)可發(fā)生變化
  • 同步:
包括voliatile變量、synchronized、顯示鎖(Explicit Lock)、原子變量
  • 如何修復(fù)多個(gè)線程訪問可變狀態(tài)是發(fā)生的錯(cuò)誤:
1.不在線程間共享該變量
2.將狀態(tài)變量變?yōu)椴豢勺兊淖兞?3.在訪問狀態(tài)變量時(shí)使用同步

2.1 什么是線程安全性

  • 無狀態(tài)對(duì)象一定是線程安全的
    • 線程訪問無狀態(tài)對(duì)象的行為不會(huì)影響其他線程中操作的正確性
@ThreadSafe
public class StatelessFactorizer implements Servlet{
    public void service(ServletRequest req,ServletResponse resp){
        BigInteger i=extractFromRequest(req);
        BigInteger[] factors=factor(i);
        encodeIntoResponse(factors,resp);
    }
}

這個(gè)例子中StatelessFactorizer就是無狀態(tài)的,他既不包含任何域,也不包含對(duì)其他類中域的引用,。計(jì)算中的臨時(shí)狀態(tài)僅存于線程棧(線程獨(dú)立)上的局部變量表中,并且只能由正在執(zhí)行的線程訪問

2.2 原子性

2.2.1 競(jìng)態(tài)條件
  • 競(jìng)態(tài)條件
當(dāng)某個(gè)計(jì)算結(jié)果的正確性取決于多線程交替執(zhí)行的時(shí)序時(shí),就會(huì)發(fā)生競(jìng)態(tài)條件,簡(jiǎn)而言之就是靠運(yùn)氣。
最常見的就是“先檢查后執(zhí)行(Check-Then-Act)”
2.2.2 示例:延遲初始化時(shí)的競(jìng)態(tài)條件
@NotThreadSafe
public class LazyInitRace{
    private ExpenciveObject instance=null;
    
    public ExpenciveObject getInstance(){
        if(instance==null){
            instance=new ExpenciveObject(); 
        }
        return instance;
    }
}

假設(shè)有兩個(gè)線程A和B同時(shí)訪問getInstance,A看到instance對(duì)象為空創(chuàng)建一個(gè)ExpenciveObject對(duì)象,B線程同樣需要判斷instance對(duì)象是否為空,但是instance是否為空需要取決于不可預(yù)測(cè)的時(shí)序,包括線程的調(diào)度方式,A線程初始化ExpenciveObject并設(shè)置instance所耗費(fèi)的時(shí)間。如果當(dāng)B線程判斷instance為空,則會(huì)新建一個(gè)ExpenciveObject對(duì)象,那么這兩個(gè)線程調(diào)用getInstance時(shí)得到的就是兩個(gè)不同的結(jié)果。

2.2.3 復(fù)合操作
  • 避免競(jìng)態(tài)條件
在某個(gè)線程修改變量時(shí),通過某種方式阻止其他線程使用這個(gè)變量。從而確保其他線程只能在修改之前或者修改之后讀取和修改狀態(tài),而不是在修改狀態(tài)的過程中
  • 原子操作
對(duì)于訪問同一個(gè)狀態(tài)的所有操作(包括該操作本身),這個(gè)操作是以原子方式執(zhí)行的操作。
假定線程A和B,如果從執(zhí)行A線程的角度來說,當(dāng)另一個(gè)線程B執(zhí)行時(shí),要么將B全部執(zhí)行完,要么完全不執(zhí)行B,那么A和B對(duì)彼此來說都是原子的
@ThreadSafe
public class CountingFactorizer implements Servlet{
    private final AtomicLong count=new AtomicLong(0);
    
    public long getCount(){
        return count.get();
    }
    
    public void service(ServletRequest req,ServletResponse resp){
        BigInteger i=extractFromRequest(req);
        BigInteger[] factors=factor(i);
        count.incrementAndGet();
        encodeIntoResponse(factors,resp);
    }
}

AtomicLong是一個(gè)原子變量類,在java.concurrent.atomic包中,該包還含有其他一些原子變量類,用于實(shí)現(xiàn)在數(shù)值和對(duì)象引用上的原子狀態(tài)轉(zhuǎn)換,上面代碼用例通過AtomicLong代替long類型計(jì)數(shù)器,能夠確保所有對(duì)計(jì)數(shù)器的訪問都是原子的,由于上面代碼Servlet狀態(tài)就是計(jì)數(shù)器的狀態(tài),并且計(jì)數(shù)器是線程安全的,因此Servlet也是線程安全的

2.3 加鎖操作

  • 對(duì)多個(gè)線程安全狀態(tài)變量操作,并不能保證線程安全。要保持狀態(tài)的一致性,就需要在單個(gè)原子操作中更新所有相關(guān)的狀態(tài)變量。
@NotThreadSafe
public class UnsafeCachingFactorizer implements Servlet{
    private final AtomicReference<BigInteger> lastNumber=new AtomicReference<BigInteger>();
    
    private final AtomicReference<BigInteger[]> lastFactors=new AtomicReference<>();
    
    public void service(ServletRequest req,ServletResponse resp){
        BigInteger i=extractFromRequest(req);
        if(i.equals(lastNumber.get())){
            encodeIntoResponse(factors,resp);
        }else{
            BigInteger[] factors=factor(i);
            lastNumber.set(i);
            lastFactors.set(factors);
            encodeIntoResponse(factors,resp);
        }
    }
}

上方代碼中,在使用原子引用的情況下,盡管對(duì)set方法的每次調(diào)用都是原子的,但仍然無法同時(shí)更新lastNumber和lastFactors。如果只修改了其中一個(gè)變量,那么在這兩次修改操作之間,其他線程將發(fā)現(xiàn)不變性條件被破壞了。同樣,也不能保證會(huì)同時(shí)獲取兩個(gè)值:在線程A獲取這兩個(gè)值得過程中,線程B可能修改了它們,這樣線程A發(fā)現(xiàn)不變性條件被破壞了

2.3.1 內(nèi)置鎖
  • 同步代碼塊(Synchronized Block稱為內(nèi)置鎖或監(jiān)視器鎖)
    • 鎖的對(duì)象引用
    • 由這個(gè)鎖保護(hù)的代碼塊(靜態(tài)的synchronized方法以class對(duì)象作為鎖)
  • 進(jìn)入同步代碼塊自動(dòng)獲得鎖,退出同步代碼塊(無論是正常退出還是拋出異常)自動(dòng)釋放鎖
  • JAVA內(nèi)置鎖相當(dāng)于一種互斥鎖,最多只有一個(gè)線程能持有這種鎖,其他未持有鎖的線程必須等待或阻塞,直到持有這個(gè)鎖的線程釋放鎖,否則將一直等待下去。
  • 由這個(gè)鎖保護(hù)的同步代碼塊會(huì)以原子方式執(zhí)行
@ThreadSafe
public class UnsafeCachingFactorizer implements Servlet{
    private final AtomicReference<BigInteger> lastNumber=new AtomicReference<BigInteger>();
    
    private final AtomicReference<BigInteger[]> lastFactors=new AtomicReference<>();
    
    public synchronized void service(ServletRequest req,ServletResponse resp){
        BigInteger i=extractFromRequest(req);
        if(i.equals(lastNumber.get())){
            encodeIntoResponse(factors,resp);
        }else{
            BigInteger[] factors=factor(i);
            lastNumber.set(i);
            lastFactors.set(factors);
            encodeIntoResponse(factors,resp);
        }
    }
}

上面代碼在service方法上加了synchronized后線程安全了,但是假如多個(gè)請(qǐng)求同時(shí)訪問這個(gè)方法,由于同一時(shí)刻只有一個(gè)線程可以執(zhí)行該方法,會(huì)導(dǎo)致性能很低。

2.3.2 重入
  • 內(nèi)置鎖是可重入的
當(dāng)一個(gè)線程試圖獲得一個(gè)已經(jīng)由他自己持有的鎖,這個(gè)請(qǐng)求可以成功。
  • 獲取鎖的粒度是“線程”不是“調(diào)用”
  • 實(shí)現(xiàn)方式
為每個(gè)鎖關(guān)聯(lián)一個(gè)獲取計(jì)數(shù)值和所有者線程,當(dāng)計(jì)數(shù)值為0則認(rèn)為該鎖沒有被任何線程持有。
當(dāng)線程請(qǐng)求一個(gè)未被持有的鎖時(shí),JVM將記下鎖的持有者,并加計(jì)數(shù)值加設(shè)為1。
如果這個(gè)線程再次獲得鎖,計(jì)數(shù)值遞增。
當(dāng)線程退出同步代碼塊時(shí),計(jì)數(shù)值遞減,當(dāng)計(jì)數(shù)值到0時(shí),這個(gè)鎖將被釋放。

2.4 用鎖來保護(hù)狀態(tài)

  • 鎖保護(hù)狀態(tài)
對(duì)于可能被多個(gè)線程訪問的可變狀態(tài)變量,在訪問它時(shí)都需要持有同一個(gè)鎖。
  • 加鎖約定
將所有的可變狀態(tài)變量(前提是需要被多個(gè)線程同時(shí)訪問)都封裝在對(duì)象內(nèi)部,并通過對(duì)象的內(nèi)置鎖對(duì)所有訪問可變狀態(tài)的代碼路徑進(jìn)行同步,使得在該對(duì)象上不會(huì)發(fā)生并發(fā)訪問
  • 對(duì)于每個(gè)包含多個(gè)變量的不變性條件,其中涉及的所有變量都要由同一把鎖保護(hù)

2.5 活躍性與性能

  • 不良并發(fā)
可同時(shí)調(diào)用的數(shù)量,不僅受到可用處理資源的限制,還受到應(yīng)用程序本身結(jié)構(gòu)的限制
  • 避免不良并發(fā)
盡量將不影響共享狀態(tài)且執(zhí)行時(shí)間較長(zhǎng)的操作從同步代碼中分離出去,從而在這些操作的執(zhí)行過程中,其他線程可以訪問共享狀態(tài)
  • 示例:
public class CachedFactorizer implements Servlet{
    private BigInteger lastNumber;
    private BigInteger[] lastFactors;
    private long hits;
    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);
    }
}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容