首頁 >Java >java教程 >Java並發程式設計之線程安全性怎麼實現

Java並發程式設計之線程安全性怎麼實現

WBOY
WBOY轉載
2023-05-21 15:14:131502瀏覽

    1.什麼是執行緒安全性

    當多個執行緒存取某個類別時,不管執行時間環境採用何種呼叫方式或這些執行緒將如何交替執行,並且在主調程式碼中不需要任何額外的同步或協同,這個類別都能表現出正確的行為,那麼就稱這個類別是線程安全的。

    無狀態的物件一定是執行緒安全的,例如:Servlet

    2.原子性

    2.1 競爭條件

    由於不恰當的執行時序而出現不正確的結果的情況,就是競爭條件。

    「先檢查後執行」操作,也就是透過一個可能實效的觀測結果來決定下一步的動作。比如:延遲初始化。

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

    「讀取-修改-寫入」的操作,其結果狀態依賴先前的狀態。如:遞增運算。

    long count = 0;
    count++;

    2.2 複合操作

    原子操作是指,對於存取同一個狀態的所有操作(包括此操作本身)來說,這個操作是以一個原子方式執行(不可分割)的操作。

    為了確保執行緒安全性,包含了一組必須以原子方式執行的操作,稱為複合操作。

    遞增運算可以使用一個現有的執行緒安全類,確保執行緒安全性。如:

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

    3.加鎖機制

    如果一個類別只有一個狀態變量,可以透過使用執行緒安全的狀態變數來確保類別的執行緒安全性。當一個類別有更多的狀態時,僅僅增加更多的線程安全狀態變數是不夠的。為了確保狀態的一致性,必須在單一原子操作中更新所有相關的狀態變數。

    3.1 內建鎖定

    Java提供一個內建鎖定:同步程式碼區塊,它包括:一個作為鎖的物件引用、一個作為由這個鎖保護的程式碼塊。

    以關鍵字synchronized來修飾的方法就是一種橫跨整個方法體的同步程式碼區塊,其中該同步程式碼區塊的鎖定就是方法呼叫所在的物件。靜態的synchronized方法以Class物件作為鎖。

    當執行緒進入同步程式碼區塊時,會自動取得鎖定;而當執行緒退出同步程式碼區塊時,會自動釋放鎖定。最多只有一個執行緒能持有這種鎖,因此同步程式碼會以原子方式執行。

    3.2 重入

    內建鎖定是可重入的,表示取得鎖的操作的粒度是線程,而不是呼叫。當一個執行緒嘗試重新取得已經由其持有的鎖時,該請求也會成功。

    重入進一步提升了加鎖行為的封裝性,簡化了物件導向並發程式碼的開發。

    public class Widget {
        public synchronized void doSomething() {
            //......
        }
    }
    public class LoggingWidget extends Widget {
        public synchronized void doSomething() {
            //......
            super.doSomething();//假如没有可重入的锁,该语句将产生死锁。
        }
    }

    4.用鎖定保護狀態

    對於可能被多個執行緒同時存取的可變狀態變量,在存取它時都需要持有同一個鎖,在這種情況下,稱狀態變數是由這個鎖保護的。

    5.活躍性與效能

    粗粒度地使用鎖,雖然確保了執行緒安全性,但可能造成效能問題和活躍度問題,如:

    @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);//因数分解计算
                lastNumber = i;
                lastFactors = factors;//存放上一次计算结果
                encodeIntoResponse(resp, factors);
            }
        }
    }

    可以透過縮小同步程式碼區塊,既確保servlet的並髮型,又維護線程安全性。不要把本應是原子操作拆分到多個同步程式碼區塊中,盡量將不影響共用狀態且執行時間較長的操作從同步程式碼中分離出來。如:

    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並發程式設計之線程安全性怎麼實現的詳細內容。更多資訊請關注PHP中文網其他相關文章!

    陳述:
    本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除