1. 程式人生 > >多線程編程之竟態

多線程編程之竟態

times run 依賴 check 不同的 寄存器 play edate spa

一.竟態

  1.竟態的概念

    竟態指計算結果的正確性依賴相對時間順序和線程的交錯,通俗的說就是計算結果與時間有關,對於一個同樣的輸入,有時候結果正確,有時候結果不正確。

    竟態不一定會導致結果錯誤,只是說有這種導致結果出錯的可能性。

  2.模擬竟態的產生

    下面有一個模擬請求Id生成器,讓多個線程隨機生成請求id.

技術分享圖片
public final class RequestIdGenerator {

    private final static RequestIdGenerator INSTANCE = new RequestIdGenerator();
    
private final static short SEQ_UPPER_LIMIT = 999; private short sequence = -1; private RequestIdGenerator() { } public static RequestIdGenerator getInstance (){ return INSTANCE; } public short nextSequence() { if (sequence >= SEQ_UPPER_LIMIT) { sequence
= 0; } else { sequence++; } return sequence; } public String nextId() { SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss"); String timeStamp = sdf.format(new Date()); DecimalFormat df =new DecimalFormat("000"); short
sequenceNo = nextSequence(); return "0049"+timeStamp+df.format(sequenceNo); } }
View Code

    測試類:

技術分享圖片
public class RaceConditionDemo {
    
    public static void main(String[] args) {
        int numberOfThreads = Runtime.getRuntime().availableProcessors();
        Thread[] workThreads = new Thread[numberOfThreads];
        System.out.println(numberOfThreads);
        for (int i = 0; i< numberOfThreads; i++) {
            workThreads[i] = new WorkerThread(i, 10);  
        }
        for (Thread t : workThreads) {
            t.start();
        }
    }
      static class WorkerThread extends Thread {
            private final int requestCount;
            public WorkerThread (int id, int requsetCount) {
                super("worker-"+id);
                this.requestCount = requsetCount;
            }
            @Override
            public void run() {
                int i =requestCount;        
                RequestIdGenerator generator = RequestIdGenerator.getInstance();
                while (i-- > -1) {
                    String requestID = generator.nextId();
                    processRequest(requestID);
                }    
            }
            private void processRequest(String requestID) {
                Tools.randomPause(50);
                System.out.println(Thread.currentThread().getName()+" "+requestID);
            }
            
      }
}
View Code

    

    按理說每個線程不同時間生成的請求id應該都是不同的,但是多次執行會發現有時候請求id是相同的。

    這也就是說執行結果正確與否與時間相關,即出現了竟態。

  3.竟態結果分析

    分析可見nextSequence()這個方法導致了不同的線程拿到了相同的requestId,因為RequestIdGenerator這個類中有一個共享的全局變量sequence,多個線程並發的讀取更新

sequence導致了竟態的出現。即一個線程對sequence所做的更新可能被其它線程的更新而覆蓋掉,導致數據出現臟讀。

4.竟態的兩種模式

    ①read-modify-write(讀-改-寫)

      即讀取一個共享變量的值(read),然後根據值做一些計算(modify),最後更新該變量的值(write).

     例如sequence++就是如此,過程指令如下:

      1.從內存中將squence的值讀取到寄存器r1中

      2.r1的值加1

      3.將r1的值更新到sequence變量的內存空間中

     在多線程環境下,某個線程執行完1後,可能其他線程已經更新了sequence的值,但是該線程卻仍然使用r1未更新的值去操作2,3指令,造成丟失更新和臟讀。

    ②check-then-act(檢測而後行動)

    以下面這段代碼為例:

public short nextSequence() {
        if (sequence >= SEQ_UPPER_LIMIT) {    //步驟1
            sequence = 0;          //步驟2.1
        } else { 
            sequence++;                  //步驟2.2
        }
        return sequence;
    }

      檢測而後行動指讀取某個共享變量的值,根據該值做一些判斷,然後根據該判斷結果去條件執行一些操作。

      在多線程環境下,可能當某個線程執行完步驟1後,其它線程更新了sequence的值,此時該線程仍然根據之前的判斷去做一些操作,也會造成丟失數據更新和臟讀。

    

  

    

多線程編程之竟態