2013-12-17 21 views
3

我碰到過的SCJP練習題之一就是在SafeDeposit類中提供了代碼。該問題的答案聲稱,如果另一個類使用多個線程,那麼非同步(非線程安全)getInstance()方法可能返回多個SafeDeposit實例。我試過了,試過了,不能得到toString()方法來表明有多個SafeDeposit實例被創建。我是否錯過了一些東西,或者這只是「可能」發生的事情之一,但確實真的不太可能發生?如何使公共靜態非同步getInstance()方法將一個私有靜態引用變量的多個實例返回給一個對象?

class SafeDeposit { 
    private static SafeDeposit sd; 
    public static SafeDeposit getInstance() { 
     if(sd == null) sd = new SafeDeposit(); 
     return sd; 
    } 
    private SafeDeposit() { } 
} 

public class PrivCon { 
    public static void main(String[] args) { 
     String checker; 
     SafeThief wizard = new SafeThief(); 
     SafeThief wizard2 = new SafeThief(); 
     for(int i = 0; i < 10; i ++) { 
      new Thread(wizard).start(); 
      new Thread(wizard2).start(); 
     } 
    } 
} 

class SafeThief implements Runnable { 
    public void run() { 
     System.out.println(SafeDeposit.getInstance().toString()); 
    } 
} 

回答

4

是那些東西,「可能」發生這只是一個,但真的,真的,真的不可能發生的?

試試這個代碼,看看如何不太可能真的是:

class SafeDeposit { 
    private static SafeDeposit sd; 
    public static SafeDeposit getInstance() { 
    if(sd == null) sd = new SafeDeposit(); 
    return sd; 
    } 
    private SafeDeposit() { } 

    static void warmup() { 
    for (int i = 0; i < 100_000; i++) getInstance(); 
    sd = null; 
    } 
} 

public class PrivCon { 
    public static void main(String[] args) { 
    SafeDeposit.warmup(); 
    SafeThief wizard = new SafeThief(); 
    for(int i = 0; i < 10; i ++) new Thread(wizard).start(); 
    } 
} 

class SafeThief implements Runnable { 
    public void run() { 
    try { Thread.sleep(100); } catch (InterruptedException e) { } 
    System.out.println(SafeDeposit.getInstance().toString()); 
    } 
} 

這是我的典型輸出:

[email protected] 
[email protected] 
[email protected] 
[email protected] 
[email protected] 
[email protected] 
[email protected] 
[email protected] 
[email protected] 
[email protected] 
在所有

幾乎沒有任何重複。

如果你想知道爲什麼,那是因爲我加了熱身代碼,這引起了getInstance()方法是JIT編譯成一個積極優化的代碼它充分利用了Java內存模型給出的自由。

我還在Runnable的開頭添加了一些sleep時間,因爲只要一個線程寫入該值,那些在該點之後開始的線程就會可靠地觀察寫入。所以最好先讓所有線程開始,然後讓他們撥打getInstance

+0

謝謝,這真的有幫助。我從來沒有見過像你的熱身代碼。我很想看看如何實現這樣的效果會影響我的其他多線程練習。 – paniclater

+0

它絕對會影響所有嘗試檢測寫入可見性問題的練習。但是,請注意,這段代碼非常脆弱,我甚至開始想知道爲什麼它的工作「很好」:'println'是一個同步方法,所以一旦你調用它,你的線程就會同步* all * its記憶與系統的其餘部分。 –

1

正確。這是線程安全的,

if(sd == null)    // Thread B here <--- 
    sd = new SafeDeposit(); // Thread A here <--- 
return sd; 

所以,如果你有線程A和B以上,你會得到你的實例辛格爾頓的兩個實例。要看到,在這樣的構造函數中添加打印方法=

private SafeDeposit() { 
    System.out.println("In SafeDeposit constructor - Should only print ONCE"); 
    try { 
    Thread.sleep(2000);  // <-- Added to help reproduce multiple 
          //  instances being created. 
    } catch (Exception e) { 
    } 
} 
+1

如果您還在該構造函數中添加Thread.sleep(2000)(模擬一些初始化工作),那麼您將更有可能看到結果。 –

+0

感謝 - 將它與線程B,線程放在一起。插入的註釋幫助我更全面地瞭解代碼如何顯然不是線程安全的,添加睡眠對於證明其不安全性質是一個很好的建議。 – paniclater

0

SafeDeposit構造函數代碼中的原子時,如果您沒有看到這個問題。要模擬更真實的情況,請將SafeDeposit構造函數更改爲下面的代碼,您將自己看到結果。

private SafeDeposit() { 
     try { 
      Thread.sleep(5000); 
     } 
     catch (InterruptedException e) {} 
    } 
+1

您錯過了這一點:這不僅僅是關於執行順序。這也是關於寫入其他線程的*可見​​性。 –

0

強調單例的方法是使用一個CountDownLatch來使一組線程一次下降。可悲的是,這段代碼無法打印除1之外的任何內容,但我懷疑這是因爲我正在單核筆記本電腦上測試它。有人會在多核CPU上測試它,看看它是否打印其他內容?

請參閱下面有關返回結果大於1的測試結果的註釋,這意味着實際創建了多個假設的單例實例。

public class Test { 

    static class SafeDeposit { 

     private static SafeDeposit sd; 

     public static SafeDeposit getInstance() { 
      if (sd == null) { 
       sd = new SafeDeposit(); 
      } 
      return sd; 
     } 

     private SafeDeposit() { 
     } 
    } 

    static final Set<SafeDeposit> deposits = Collections.newSetFromMap(new ConcurrentHashMap<SafeDeposit,Boolean>()); 

    static class Gun implements Runnable { 
     private final CountDownLatch wait; 

     public Gun (CountDownLatch wait) { 
      this.wait = wait; 
     } 

     @Override 
     public void run() { 
      try { 
       // One more thread here and ready. 
       wait.countDown(); 
       // Wait for the starting pistol. 
       wait.await(); 
       // Grab an instance - nnnnnnnnow!!!. 
       SafeDeposit safe = SafeDeposit.getInstance(); 
       // Store it in the Set. 
       deposits.add(safe); 
      } catch (InterruptedException ex) { 
       Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex); 
      } 

     } 
    } 

    // Use that many Threads 
    private static final int ArmySize = 1000; 

    public static void main(String[] args) throws InterruptedException { 
     // The Latch will wait for all threads to be ready. 
     CountDownLatch latch = new CountDownLatch(ArmySize); 
     Thread[] threads = new Thread[ArmySize]; 
     for (int i = 0; i < ArmySize; i++) { 
      // Make all threads and start them. 
      threads[i] = new Thread(new Gun(latch)); 
      threads[i].start(); 
     } 
     // Wait for all to complete. 
     for (int i = 0; i < ArmySize; i++) { 
      threads[i].join(); 
     } 
     // How many unique Safes did we et? 
     System.out.println(deposits.size()); 
    } 
} 
+0

我現在已經做了很多個小時的java練習,並且太累了,但是我會在我的多核機器上運行你的代碼,看看明天會發生什麼。感謝您花時間編寫代碼併發布。 – paniclater

+1

在我的4核心AMD上,我得到'3'。 –

+0

@ElliottFrisch - 謝謝!確認!我的Core i7上也有3個。 – OldCurmudgeon

相關問題