2013-12-17 5 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.

Я также добавил sleep время до начала Runnable, потому что как только один поток записывает значение, те потоки, которые начинаются после этой точки, будут надежно наблюдать за записью. Поэтому лучше сначала начать все потоки, затем позвольте им позвонить getInstance.

+0

Спасибо, это действительно помогает. Я никогда не видел ничего похожего на ваш код разминки. Мне интересно посмотреть, как реализация чего-то подобного влияет на мои другие многопоточные упражнения. – paniclater

+0

Он определенно влияет на все упражнения, которые пытаются обнаружить проблемы с видимостью записи. Однако обратите внимание, что этот код очень незначительный, и я даже начал задаваться вопросом, почему он работает так «хорошо»: 'println' - это синхронизированный метод, поэтому, как только вы его назовете, ваш поток будет синхронизирован * all * его память с остальной частью системы. –

1

Исправить. Это НЕ поточно,

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

Так что если у вас есть темы А и В, как описано выше, вы получите два экземпляра вашей Singleton экземпляры. Чтобы увидеть его, добавить метод печати в конструкторе, как это =

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, но я подозреваю, что это потому, что я тестирую его на одноядерном ноутбуке. Кто-нибудь проверит его на многоядерном процессоре и посмотрит, не распечатает ли он что-нибудь еще?

См. Комментарии ниже для результатов результатов теста, возвращающих результат> 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 - Спасибо! Подтверждено! Я также получаю 3 на моем Core i7. – OldCurmudgeon