多線程編程之竟態
一.竟態
1.竟態的概念
竟態指計算結果的正確性依賴相對時間順序和線程的交錯,通俗的說就是計算結果與時間有關,對於一個同樣的輸入,有時候結果正確,有時候結果不正確。
竟態不一定會導致結果錯誤,只是說有這種導致結果出錯的可能性。
2.模擬竟態的產生
下面有一個模擬請求Id生成器,讓多個線程隨機生成請求id.
public final class RequestIdGenerator { private final static RequestIdGenerator INSTANCE = new RequestIdGenerator();View Codeprivate 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"); shortsequenceNo = nextSequence(); return "0049"+timeStamp+df.format(sequenceNo); } }
測試類:
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的值,此時該線程仍然根據之前的判斷去做一些操作,也會造成丟失數據更新和臟讀。
多線程編程之竟態