溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java并發(fā)編程之線程安全性怎么實(shí)現(xiàn)

發(fā)布時(shí)間:2022-04-07 15:51:43 來(lái)源:億速云 閱讀:107 作者:iii 欄目:開(kāi)發(fā)技術(shù)

今天小編給大家分享一下Java并發(fā)編程之線程安全性怎么實(shí)現(xiàn)的相關(guān)知識(shí)點(diǎn),內(nèi)容詳細(xì),邏輯清晰,相信大部分人都還太了解這方面的知識(shí),所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來(lái)了解一下吧。

    1.什么是線程安全性

    當(dāng)多個(gè)線程訪問(wèn)某個(gè)類時(shí),不管運(yùn)行時(shí)環(huán)境采用何種調(diào)用方式或者這些線程將如何交替執(zhí)行,并且在主調(diào)代碼中不需要任何額外的同步或協(xié)同,這個(gè)類都能表現(xiàn)出正確的行為,那么就稱這個(gè)類是線程安全的。

    無(wú)狀態(tài)的對(duì)象一定是線程安全的,比如:Servlet

    2.原子性

    2.1 競(jìng)爭(zhēng)條件

    由于不恰當(dāng)?shù)膱?zhí)行時(shí)序而出現(xiàn)不正確的結(jié)果的情況,就是競(jìng)爭(zhēng)條件。

    “先檢查后執(zhí)行”操作,即通過(guò)一個(gè)可能實(shí)效的觀測(cè)結(jié)果來(lái)決定下一步的動(dòng)作。比如:延遲初始化。

    if(instance == null) {
        instance = new SomeObject();
    }

    “讀取-修改-寫入”的操作,其結(jié)果狀態(tài)依賴于之前的狀態(tài)。如:遞增運(yùn)算。

    long count = 0;
    count++;

    2.2 復(fù)合操作

    原子操作是指,對(duì)于訪問(wèn)同一個(gè)狀態(tài)的所有操作(包括此操作本身)來(lái)說(shuō),這個(gè)操作是以一個(gè)原子方式執(zhí)行(不可分割)的操作。

    為了確保線程安全性,包含了一組必須以原子方式執(zhí)行的操作,稱為復(fù)合操作。

    遞增運(yùn)算可以使用一個(gè)現(xiàn)有的線程安全類,確保線程安全性。如:

    AtomicLong count = new AtomicLong(0);
    count.incrementAndGet();

    3.加鎖機(jī)制

    當(dāng)類只有一個(gè)狀態(tài)變量時(shí),可以通過(guò)線程安全的狀態(tài)變量來(lái)維護(hù)類的線程安全性。但如果類有更多的狀態(tài)時(shí),就不能只添加更多線程安全的狀態(tài)變量了。要保持狀態(tài)的一致性,就需要在單個(gè)原子操作中更新所以相關(guān)的狀態(tài)變量。

    3.1 內(nèi)置鎖

    Java提供一種內(nèi)置鎖:同步代碼塊,它包括:一個(gè)作為鎖的對(duì)象引用、一個(gè)作為由這個(gè)鎖保護(hù)的代碼塊。

    以關(guān)鍵字synchronized來(lái)修飾的方法就是一種橫跨整個(gè)方法體的同步代碼塊,其中該同步代碼塊的鎖就是方法調(diào)用所在的對(duì)象。靜態(tài)的synchronized方法以Class對(duì)象作為鎖。

    線程在進(jìn)入同步代碼塊之前會(huì)自動(dòng)獲得鎖,在退出同步代碼塊是自動(dòng)釋放鎖。最多只有一個(gè)線程能持有這種鎖,因此同步代碼會(huì)以原子方式執(zhí)行。

    3.2 重入

    內(nèi)置鎖是可重入的,意味著獲取鎖的操作的粒度是線程,不是調(diào)用。當(dāng)某個(gè)線程試圖獲得一個(gè)已經(jīng)由它自己持有的鎖時(shí),這個(gè)請(qǐng)求也會(huì)成功。

    重入進(jìn)一步提升了加鎖行為的封裝性,簡(jiǎn)化了面向?qū)ο蟛l(fā)代碼的開(kāi)發(fā)。

    public class Widget {
        public synchronized void doSomething() {
            //......
        }
    }
    public class LoggingWidget extends Widget {
        public synchronized void doSomething() {
            //......
            super.doSomething();//假如沒(méi)有可重入的鎖,該語(yǔ)句將產(chǎn)生死鎖。
        }
    }

    4.用鎖保護(hù)狀態(tài)

    對(duì)于可能被多個(gè)線程同時(shí)訪問(wèn)的可變狀態(tài)變量,在訪問(wèn)它時(shí)都需要持有同一個(gè)鎖,在這種情況下,稱狀態(tài)變量是由這個(gè)鎖保護(hù)的。

    5.活躍性與性能

    粗粒度地使用鎖,雖然確保了線程安全性,但可能造成性能問(wèn)題和活躍度問(wèn)題,如:

    @ThreadSafe
    public class SynchronizedFactorizer 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);//因數(shù)分解計(jì)算
                lastNumber = i;
                lastFactors = factors;//存放上一次計(jì)算結(jié)果
                encodeIntoResponse(resp, factors);
            }
        }
    }

    可以通過(guò)縮小同步代碼塊,既確保servlet的并發(fā)型,又維護(hù)線程安全性。不要把本應(yīng)是原子操作拆分到多個(gè)同步代碼塊中,盡量將不影響共享狀態(tài)且執(zhí)行時(shí)間較長(zhǎng)的操作從同步代碼中分離出來(lái)。如:

    public class CachedFactorizer implements Servlet {
        @GuardedBy("this") private BigInteger lastNumber;
        @GuardedBy("this") private BigInteger[] lastFactors;
    
        public void service(ServletRequest req, ServletResponse resp) {
            BigInteger i = extractFromRequest(req); 
            BigInteger[] factors = null;      
            synchronized (this) {
                if (i.equals(lastNumber)) {
                   factors = lastFactors.clone();
                }         
            }    
            if (factors == null) {        
                factors = factor(i);
                synchronized (this) {
                   lastNumber = i;
                   lastFactors = factors.clone();
                }
            }
            encodeIntoResponse(resp, factors);
        }
    }

    以上就是“Java并發(fā)編程之線程安全性怎么實(shí)現(xiàn)”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會(huì)為大家更新不同的知識(shí),如果還想學(xué)習(xí)更多的知識(shí),請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI