簡體   English   中英

如何測試線程之間的值的可見性

[英]How to test visibility of values between threads

在線程之間測試價值可見性的最佳方法是什么?

class X {
 private volatile Object ref;

 public Object getRef() {
  return ref;
 }

 public void setRef(Object newRef) {
  this.ref = newRef;
 }
}

類X公開了對ref對象的ref 如果並發線程讀取並寫入對象引用,則每個Thread必須查看已設置的最新對象。 volatile修飾符應該這樣做。 這里的實現是一個示例,它也可以是同步的或基於鎖的實現。

現在我正在尋找一種方法來編寫一個測試,告知我什么時候值可見性沒有達到指定值(讀取舊值)。

如果測試確實會燒掉一些cpu周期,那也沒關系。

如果我理解正確,那么你想要一個測試用例,如果變量被定義為volatile而傳遞,如果沒有則失敗。

但是我認為沒有可靠的方法來做到這一點。 根據jvm並發訪問的實現,即使沒有volatile,也可以正常工作。

因此,如果指定了volatile,單元測試正常工作,但仍然可以在沒有volatile的情況下正常工作。

JLS說明了為了在涉及“線程間操作”的應用程序中獲得有保證的一致執行,您應該做些什么。 如果您不執行這些操作,則執行可能會不一致。 但是它實際上是否會不一致取決於您使用的JVM,您正在使用的硬件,應用程序,輸入數據以及......運行應用程序時機器上可能發生的任何其他情況。

我看不出你提出的測試會告訴你什么。 如果測試顯示執行不一致,則會確認正確進行同步的智慧。 但是如果運行測試幾次只顯示(顯然)一致的執行,這並不能告訴你執行總是一致的。


例:

假設您在(例如)JDK 1.4.2(rev 12)/ Linux / 32bit上運行測試,其中“客戶端”JVM和選項x,y,z在單個處理器計算機上運行。 在運行測試1000次后,您會發現如果省略不 volatile它似乎沒有任何區別 你在那種情況下實際學到了什么?

  • 你還沒知道它真的沒什么區別? 如果您更改測試以使用更多線程等,您可能會得到不同的答案。 如果你運行測試幾千或幾百萬或十億次,你可能得到一個不同的答案。
  • 您還沒有了解其他平台可能發生的事情; 例如,不同的Java版本,不同的硬件或不同的系統負載條件。
  • 你還沒知道是否可以安全地省略volatile關鍵字。

如果測試顯示出差異,您只能學到一些東西。 而你所學到的唯一一點是同步很重要 ......這就是所有教科書等一直在告訴你的事情:-)


底線:這是最糟糕的黑盒測試。 它沒有讓你真正了解盒子里面發生了什么。 要獲得這種洞察力,您需要1)了解內存模型和2)深入分析JIT編譯器發出的本機代碼(在多個平台上......)

哇,這比我最初的想法要難得多。 我可能會完全離開,但是這個怎么樣?

class Wrapper {
    private X x = new X();
    private volatile Object volatileRef;
    private final Object setterLock = new Object();
    private final Object getterLock = new Object();

    public Object getRef() {
        synchronized(getterLock) {
            Object refFromX = x.getRef();
            if (refFromX != volatileRef) {
                // FAILURE CASE!
            }
            return refFromX;
        }
    }

    public void setRef(Object ref) {
        synchronized(setterLock) {
            volatileRef = ref;
            x.setRef(ref);
        }
    }
}

這有用嗎? 當然,你必須創建許多線程來命中這個包裝器,希望出現壞的情況。

這個怎么樣 ?

public class XTest {

 @Test
 public void testRefIsVolatile() {
  Field field = null;
  try {
   field = X.class.getDeclaredField("ref");
  } catch (SecurityException e) {
   e.printStackTrace();
   Assert.fail(e.getMessage());
  } catch (NoSuchFieldException e) {
   e.printStackTrace();
   Assert.fail(e.getMessage());
  }
  Assert.assertNotNull("Ref field", field);
  Assert.assertTrue("Is Volatile", Modifier.isVolatile(field
    .getModifiers()));
 }

}

所以基本上你想要這個場景:一個線程寫入變量,而另一個線程同時讀取它,並且你想確保變量read具有正確的值,對吧?

好吧,我不認為你可以使用單元測試,因為你無法確保合適的環境。 這是由JVM完成的,它是如何調度指令的。 這就是我要做的。 使用調試器。 啟動一個線程來寫入數據並在執行此操作的行上放置一個斷點。 啟動第二個線程並讓它讀取數據,同時停止。 現在,執行第一個線程來執行寫入的代碼,然后使用第二個線程進行讀取。 在您的示例中,您將無法實現此目的,因為讀取和寫入是單個指令。 但通常如果這些操作更復雜,您可以交替執行兩個線程並查看是否一切都是一致的。

這需要一些時間,因為它不是自動化的。 但我不會去寫一個單元測試,嘗試讀寫很多次,希望能找到失敗的情況,因為你不會做任何事情。 單元測試的作用是確保您編寫的代碼按預期工作。 但是在這種情況下,如果測試通過,你就不會確定是否有任何感覺。 也許這只是幸運的,沖突並沒有出現在這次運行中。 這就失敗了。

暫無
暫無

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

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