Java Singleton та синхронізація


117

Будь ласка, уточніть мої запитання щодо Singleton та Multithreading:

  • Який найкращий спосіб реалізувати Singleton на Java у багатопотоковому середовищі?
  • Що відбувається, коли кілька потоків намагаються отримати доступ до getInstance() методу одночасно?
  • Чи можемо ми зробити одинарних getInstance() synchronized?
  • Чи справді потрібна синхронізація під час використання класів Singleton?

Відповіді:


211

Так, це необхідно. Існує кілька методів, які ви можете використовувати для досягнення безпеки потоку при ледачій ініціалізації:

Драконівська синхронізація:

private static YourObject instance;

public static synchronized YourObject getInstance() {
    if (instance == null) {
        instance = new YourObject();
    }
    return instance;
}

Це рішення вимагає синхронізації кожної нитки, коли насправді потрібно лише перших кількох.

Подвійна перевірка синхронізації :

private static final Object lock = new Object();
private static volatile YourObject instance;

public static YourObject getInstance() {
    YourObject r = instance;
    if (r == null) {
        synchronized (lock) {    // While we were waiting for the lock, another 
            r = instance;        // thread may have instantiated the object.
            if (r == null) {  
                r = new YourObject();
                instance = r;
            }
        }
    }
    return r;
}

Це рішення гарантує, що лише перші кілька потоків, які намагаються придбати ваш одиночний, повинні пройти процес придбання блокування.

Ініціалізація на вимогу :

private static class InstanceHolder {
    private static final YourObject instance = new YourObject();
}

public static YourObject getInstance() {
    return InstanceHolder.instance;
}

Це рішення використовує гарантії моделі пам'яті Java щодо ініціалізації класу для забезпечення безпеки потоку. Кожен клас можна завантажити лише один раз, і він завантажиться лише тоді, коли це буде потрібно. Це означає, що перший getInstanceвиклик InstanceHolderбуде завантажений і instanceбуде створений, а оскільки це контролюється ClassLoaders, додаткова синхронізація не потрібна.


23
Попередження - будьте обережні при подвійній перевіреній синхронізації. Він не працює належним чином з JVM, що передує Java, через "проблеми" з моделлю пам'яті.
Стівен C

3
-1 Draconian synchronization і Double check synchronizationgetInstance () - метод повинен бути статичним!
Грим

2
@PeterRader Вони не потрібно бутиstatic , але це могло б мати більше сенсу , якщо б вони були. Змінено за потребою.
Джефрі

4
Ви впровадження подвійного перевіреного блокування не гарантовано працює. Це фактично пояснено у статті, яку ви цитували, для подвійного перевіреного блокування. :) Тут є приклад використання летючої речовини, яка працює належним чином на 1,5 і вище (подвійне перевірене блокування просто прорветься нижче 1,5). Ініціалізація власника попиту, також зазначена у статті, ймовірно, буде більш простим рішенням у вашій відповіді.
stuckj

2
@MediumOne AFAIK, rне потрібен для коректності. Це просто оптимізація, щоб уникнути доступу до мінливого поля, оскільки це набагато дорожче, ніж доступ до локальної змінної.
Джефрі

69

Цей візерунок робить безпечну для потоків ледачу ініціалізацію екземпляра без явної синхронізації!

public class MySingleton {

     private static class Loader {
         static final MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return Loader.INSTANCE;
     }
}

Це працює тому, що він використовує завантажувач класів, щоб безкоштовно здійснити всю синхронізацію для вас: Клас MySingleton.Loaderспочатку отримує доступ до getInstance()методу, тому Loaderклас завантажується при getInstance()першому виклику. Крім того, завантажувач класів гарантує, що вся статична ініціалізація буде завершена до того, як ви отримаєте доступ до класу - саме це забезпечує безпеку потоку.

Це як магія.

Насправді він дуже схожий на схему перерахунку Джуртадо, але я вважаю, що модель Enum є зловживанням концепцією enum (хоча це і працює)


11
Синхронізація все ще присутня, вона просто виконується JVM замість програміста.
Джеффрі

@ Джефрі Ви маєте рацію, звичайно, - я все це набирав (див. Правки)
богемський

2
Я розумію, що це не має ніякого значення для JVM, я просто кажу, що це змінило для мене, що стосується самодокументованого коду. Я просто ніколи не бачив жодного шапки в Java без "остаточного" ключового слова раніше (або перерахунку), я отримав трохи пізнавального дисонансу. Для когось, хто програмує Java на повний робочий день, це, ймовірно, не змінило б, але якщо ви перескакуєте мови туди-сюди, це допомагає бути явним. Дітто для новачків. Хоча, я впевнений, що можна досить швидко адаптуватися до цього стилю; всіх шапок, мабуть, достатньо. Не маю на увазі вибирати ніт, мені сподобався ваш пост.
Рубі

1
Відмінна відповідь, хоча я не отримав якоїсь частини. Чи можете ви пояснити, "Далі, завантажувач класів гарантує, що статична ініціалізація буде завершена до того, як ви отримаєте доступ до класу - саме це забезпечує безпеку потоків". , як це допомагає в безпеці ниток, я трохи збентежений з цього приводу.
gaurav jain

2
@ wz366 насправді, хоча це не обов'язково, я погоджуюсь із стильових причин (оскільки він фактично остаточний, оскільки жоден інший код не може отримати доступ до нього) finalслід додати. Зроблено.
богем

21

Якщо ви працюєте в багатопотоковому середовищі на Java і вам потрібно гарантувати, що всі ці потоки мають доступ до єдиного примірника класу, ви можете використовувати Enum. Це матиме додаткову перевагу, що допоможе вам впоратися з серіалізацією.

public enum Singleton {
    SINGLE;
    public void myMethod(){  
    }
}

а потім просто попросіть ваші теми використовувати ваш примірник, наприклад:

Singleton.SINGLE.myMethod();

8

Так, потрібно зробити getInstance() синхронізовані. Якщо цього немає, може виникнути ситуація, коли можна зробити кілька екземплярів класу.

Розглянемо випадок, коли у вас є дві нитки, які дзвонять getInstance()одночасно. А тепер уявіть, що T1 виконується лише через instance == nullперевірку, і тоді T2 запускається. У цей момент часу екземпляр не створюється або встановлюється, тому T2 передасть перевірку і створить екземпляр. А тепер уявіть, що виконання повертається до T1. Тепер сингл створено, але T1 вже зробив перевірку! Він буде продовжувати робити об’єкт знову! ВиготовленняgetInstance() синхронізованих запобігає цю проблему.

Є кілька способів зробити одиночні нитки безпечними, але getInstance()синхронізувати, мабуть, найпростіше.


Чи допоможе це, ввівши код створення об’єкта в блок синхронізації, а не здійснювати синхронізацію всього методу?
RickDavis

@RaoG Ні. Ви хочете як перевірити, так і створити в блоці синхронізації. Вам потрібно, щоб ці дві операції відбувалися разом без перерв, або може трапитися ситуація, яку я описав вище.
Олексі

7

Енум одиночний

Найпростіший спосіб реалізувати Singleton, захищений від потоків, - це використання Enum

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

Цей код працює з моменту впровадження Enum в Java 1.5

Перевірено блокування

Якщо ви хочете зашифрувати «класичний» синглтон, який працює у багатопотоковому середовищі (починаючи з Java 1.5), слід скористатися цим.

public class Singleton {

  private static volatile Singleton instance = null;

  private Singleton() {
  }

  public static Singleton getInstance() {
    if (instance == null) {
      synchronized (Singleton.class){
        if (instance == null) {
          instance = new Singleton();
        }
      }
    }
    return instance ;
  }
}

Це не є безпечним для потоків до 1,5, оскільки реалізація летючого ключового слова була різною.

Раннє завантаження Singleton (працює навіть перед Java 1.5)

Ця реалізація інстанціює синглтон при завантаженні класу та забезпечує безпеку потоку.

public class Singleton {

  private static final Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }

  public void doSomething(){
    System.out.println("This is a singleton");
  }

}

2

Ви також можете використовувати статичний блок коду, щоб створити екземпляр при завантаженні класу та запобігти проблемам синхронізації потоків.

public class MySingleton {

  private static final MySingleton instance;

  static {
     instance = new MySingleton();
  }

  private MySingleton() {
  }

  public static MySingleton getInstance() {
    return instance;
  }

}

@Vimsha Кілька інших речей. 1. Ви повинні зробити instanceостаточний 2. Ви повинні getInstance()статичні.
Джон Вінт

Що б ви зробили, якщо хочете створити нитку в одиночному.
Арун Джордж

@ arun-george використовуйте пул потоків, пул з однією ниткою, якщо це потрібно, і оточіть деякий час (справжній) -try-catch-metable, якщо ви хочете, щоб ваша нитка ніколи не вмирала, незалежно від помилки?
tgkprog

0

Який найкращий спосіб реалізувати Singleton на Java у багатопотоковому середовищі?

Дивіться цю публікацію, щоб найкращим чином реалізувати Singleton.

Який ефективний спосіб реалізувати однотонну схему в Java?

Що відбувається, коли кілька потоків намагаються отримати доступ до методу getInstance () одночасно?

Це залежить від способу, який ви реалізували. Якщо ви використовуєте подвійне блокування без змінної змінної, ви можете отримати частково побудований об'єкт Singleton.

Детальніше див. У цьому питанні:

Чому в цьому прикладі використовується непостійний замок із подвійним перевіреним блокуванням

Чи можемо ми зробити синхронізацію getInstance () сингла?

Чи справді потрібна синхронізація під час використання класів Singleton?

Не потрібно, якщо реалізувати Singleton способами нижче

  1. статична інтиталізація
  2. перерахувати
  3. LazyInitalaization з ініціалізацією на вимогу_holder_idiom

Детальніше про це зверніться до цього питання

Шаблон дизайну Java Singleton: Питання


0
public class Elvis { 
   public static final Elvis INSTANCE = new Elvis();
   private Elvis () {...}
 }

Джерело: Ефективна Java -> Пункт 2

Він пропонує використовувати його, якщо ви впевнені, що клас завжди залишатиметься однотонним.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.