簡體   English   中英

證明以下代碼不是線程安全的

[英]Proving the following code not thread safe

如何通過編寫一些代碼來快速證明以下類不是線程安全的(因為它使用惰性初始化而不使用同步)? 換句話說,如果我正在測試以下類的線程安全性,我怎么能失敗呢?

public class LazyInitRace {
  private ExpensiveObject instance = null;

  public ExpensiveObject getInstance() {
     if (instance == null)
        instance = new ExpensiveObject();
    return instance;
  }
}

根據定義,除非您控制線程調度程序(您沒有),否則無法確定性地測試競爭條件。 您可以做的最接近的事情是在getInstance()方法中添加可配置的延遲,或者編寫問題可能顯示的代碼並在循環中運行數千次。

順便說一句,這些都不構成“證明”。 即使對於相對少量的代碼, 正式驗證也會非常非常困難。

你可以強迫ExpensiveObject在你的測試中花費很長時間嗎? 如果是這樣,只需從兩個不同的線程調用兩次getInstance() ,在足夠短的時間內第一個構造函數在第二次調用之前就不會完成。 您將最終構建兩個不同的實例,這是您應該失敗的地方。

天真的雙重檢查鎖定失敗將會更加困難,請注意......(即使沒有為變量指定volatile也不安全)。

這不是使用代碼,但這是我如何證明它的一個例子。 我忘記了這樣的執行圖的標准格式,但其含義應該足夠明顯。

| Thread 1              | Thread 2              |
|-----------------------|-----------------------|
| **start**             |                       |
| getInstance()         |                       |
| if(instance == null)  |                       |
| new ExpensiveObject() |                       |
| **context switch ->** | **start**             |
|                       | getInstance()         |
|                       | if(instance == null)  | //instance hasn't been assigned, so this check doesn't do what you want
|                       | new ExpensiveObject() |
| **start**             | **<- context switch** |
| instance = result     |                       |
| **context switch ->** | **start**             |
|                       | instance = result     |
|                       | return instance       |
| **start**             | **<- context switch** |
| return instance       |                       |

由於這是Java,因此您可以使用thread-weaver庫在代碼中注入暫停或中斷並控制多個執行線程。 這樣你就可以獲得一個緩慢的ExpensiveObject構造函數,而不必修改構造函數代碼,正如其他人(正確)建議的那樣。

嗯...這段代碼的結果將是假的,你期望的是真的。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class LazyInitRace {

    public class ExpensiveObject {
        public ExpensiveObject() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
            }
        }
    }

    private ExpensiveObject instance = null;

    public ExpensiveObject getInstance() {
        if (instance == null)
            instance = new ExpensiveObject();
        return instance;
    }

    public static void main(String[] args) {
        final LazyInitRace lazyInitRace = new LazyInitRace();

        FutureTask<ExpensiveObject> target1 = new FutureTask<ExpensiveObject>(
                new Callable<ExpensiveObject>() {

                    @Override
                    public ExpensiveObject call() throws Exception {
                        return lazyInitRace.getInstance();
                    }
                });
        new Thread(target1).start();

        FutureTask<ExpensiveObject> target2 = new FutureTask<ExpensiveObject>(
                new Callable<ExpensiveObject>() {

                    @Override
                    public ExpensiveObject call() throws Exception {
                        return lazyInitRace.getInstance();
                    }
                });
        new Thread(target2).start();

        try {
            System.out.println(target1.get() == target2.get());
        } catch (InterruptedException e) {
        } catch (ExecutionException e) {
        }
    }
}

在構造函數中進行一個非常長的計算:

public ExpensiveObject()
{
    for(double i = 0.0; i < Double.MAX_VALUE; ++i)
    {
        Math.pow(2.0,i);
    }
}

如果MAX_VALUE花費太長時間,您可能希望將終止條件降低到Double.MAX_VALUE/2.0或除以更大的數字。

您可以使用調試器輕松證明它。

  1. 編寫一個在兩個獨立線程上調用getInstance()的程序。
  2. 在ExpensiveObject的構造上設置斷點。 確保調試器只掛起線程,而不是VM。
  3. 然后當第一個線程在斷點處停止時,將其暫停。
  4. 當第二個線程停止時,您只需繼續。
  5. 如果檢查兩個線程的getInstance()調用的結果,它們將引用不同的實例。

這樣做的好處是你實際上並不需要ExpensiveObject,任何Object實際上都會產生相同的結果。 您只是使用調試器來安排執行特定代碼行,從而創建確定性結果。

好吧,它不是線程安全的。 線程安全性的證明是隨機的,但相當簡單:

  1. 使ExpensiveObject構造函數完全安全:

    synchronized ExpensiveObject(){...

  2. 放置構造函數代碼,檢查是否存在另一個對象副本 - 然后引發異常。

  3. 創建線程安全方法以清除'instance'變量

  4. 將getInstance / clearInstance的順序代碼放入循環以供多個線程執行並等待(2)異常

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM