Вступ: Одиночний візерунок в багатопрочитаних інженерних додатках

Патерн Одинонтон є одним з найбільш широко використовуваних моделей дизайну в програмній інженерії. Він забезпечує, що клас має тільки один екземпляр і забезпечує глобальну точку доступу до цієї інстанції. У однопрочитаних додатках, реалізація синглону є прямим: зробити конструктор приватний, забезпечити статичний метод, який повертає єдиний екземпляр, створений eagerly або lazily. Однак в багатопоточних інженерних додатках - так як вбудовані системи, високочастотні торгові платформи, системи реального часу управління і розподілені бази даних - проблема стає значно більш складною. Нитка безпеки, пам'ять видимість і обмеження продуктивності вимагають ретельного дизайну. Кілька недоліків в однотонному виконанні може призвести до завершення помилок

У статті розглянуто найбільш поширені помилки розробникам, які роблять при реалізації шаблону Oneton в багатопрофільних середовищах, пояснює основні причини, і забезпечує комплексний набір кращих практик і шаблонів, щоб уникнути їх. Також вона включає практичні приклади коду в Java, з посиланнями на еквівалентні візерунки в C++ і C#, і рекомендує зовнішні ресурси для подальшого читання.

Загальні збори в реалізації однотонної

У випадку, якщо вони не мають жодних проблем, то вони не можуть бути використані для того, щоб вони не могли бути використані.

1. Не роблячи конструктора приватним

Фундація будь-якого однотону є приватним конструктором, який запобігає зовнішній миттєвості. Якщо конструктор доступний (громадський, захищений, пакет-приват), будь-яка нитка може створити новий екземпляр, розбиття контракту. У багатопоточному коді це може статися незворотно, коли клас рефакторований, а видимість конструктора випадково змінюється, або коли клас підкласований (хоча підкласування однотону зазвичай не має значення). Завжди оголошуйте конструктора приватним, і якщо необхідно підтримувати підкласи (ре), скористайтеся захищеним конструктором з екстремальним обережністю і документом очікуваної поведінки.

2. В'їзд до безпеки ручних ниток

У однопоточному середовищі, простий ініціалізація ледачі працює відмінно:

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

Але в багатопоточному застосуванні два або більше ниток можна терміново ввести перевірте перед будь-якою ниткою створив екземпляр. Кожна нитка потім приступає до створення власної об'єкта, що порушує візерунок. Це класичний стану об'єктива], який призводить до багаторазових екземплярів і може призвести до невідповідного стану або витоків ресурсів.

3. Використання засмаги ініціалізації без синхронізації проперця

Навіть розробники, які розпізнають необхідність у безпеці ниток, часто додають синхронізацію нав’язливо. Наприклад, синхронізація всього методу , але вводить продуктивність пляшечки:

public static synchronized Singleton getInstance() { ... }

Кожен виклик отримує і випускає замок, навіть після того, як екземпляр вже створений. У сценаріїв високого вмісту, це наклад може сильно деградувати пропускну здатність. Кращий підхід полягає в використанні подвійно-зчеплений замок] (розміщений нижче), але навіть цей візерунок має підводні камені, якщо не впровадили правильно.

4. Синхронізація перевищення

Синхронізація надходить в багатьох формах: методи, блоки, , , і т.д. Попередження — закріплення замків коарсе-гранінних замків при наявності дрібнозернистого контролю — веде до непотрібного змісту. У деяких інженерних додатках (наприклад, в режимі реального часу системи з суворими затримками, навіть кілька сотень наносекундів замка може бути неприйнятним. Мета полягає в мінімізації критичного розділу, поки ще забезпечена безпека ниток.

5. Ігнорування volatile Keyword

На мовах, як Java, C#, і C++ (з ), volatile] ключове слово (або еквівалент) є важливим для коректної видимості в багаточитуваному коді. Без нього компільєр або процесор може повторювати інструкції, а зміни, внесені однією ниткою, можуть бути не видимі іншому. У подвійному замиканому шаблоні, не вдається заявити екземпляр однотону, як ] може викликати нитку, щоб побачити частково вбудований предмет, що веде до непередбачуваної поведінки. Це один з найбільш тонких і небезпечних помилок.

Кращі практики впровадження єдиного компонента Thread-Safe

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

Приватний конструктор та статична інтенсивність

Незалежно від стратегії ініціалізації конструктор повинен бути приватним. Єдиний екземпляр повинен зберігатися в статичному полі. Не піддається конструюванню будь-яким чином, і розглянемо, що робить клас в Java (або в C#) для запобігання підкласів.

Використовуйте Синхронізовані блоки тільки при необхідності

Для замшевої ініціалізації подвійним знімком знімається синхронізація накладної:

public class Singleton {
 private static volatile Singleton instance;

 private Singleton() {}

 public static Singleton getInstance() {
 Singleton result = instance; // Local variable for performance
 if (result == null) {
 synchronized (Singleton.class) {
 result = instance;
 if (result == null) {
 instance = result = new Singleton();
 }
 }
 }
 return result;
 }
}

У цьому коді перевірте за межі синхронізації блоку не уникає замка накладної, коли екземпляр вже існує. Внутрішній контроль забезпечує, що тільки одна нитка створює екземпляр. ключове слово запобігає переадресації інструкції і забезпечує, що призначення повністю видима на інші нитки. Зверніть увагу, що ми вбиваємо екземпляр в локальну змінну для виконання. Цей шаблон правильний у Java 5+ (з правильною моделлю пам'яті) і працює аналогічно в C# і C++ (виходячи з з замовленням пам'яті).

Ініціалізація

Якщо в однотоні завжди потрібен і створення є дешевим, ініціалізація eager - найпростіший підхід до роботи з різьбою:

public class Singleton {
 private static final Singleton INSTANCE = new Singleton();

 private Singleton() {}

 public static Singleton getInstance() {
 return INSTANCE;
 }
}

Завантаження класу властиво синхронізуватися JVM, тому не потрібна додаткова координація. Однак це створює екземпляр в класі час завантаження, який може бути небажаним в ресурсозміцних системах або коли однотон залежить від конфігурації робочого часу, що ще не доступний.

Статичний тримач шаблон (ініціалізація-на-деменд)

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

public class Singleton {
 private Singleton() {}

 private static class Holder {
 static final Singleton INSTANCE = new Singleton();
 }

 public static Singleton getInstance() {
 return Holder.INSTANCE;
 }
}

клас завантажується тільки тоді, коли вперше називається, і JVM гарантує безпечне видання статичного поля під час завантаження класу. Це широко розглядається як найтонші рішення для Java-одинонтонів.

Енум-Базед однотон (Жава)

Джошуа Бокче Ефективний Java рекомендує використовувати енум:

public enum Singleton {
 INSTANCE;

 // methods and fields
}

Константи енму несуть , і Java мова гарантує, що екземпляри енму створюються тільки один раз, навіть під послідовністю або атаками відбиття. Це як нижковий, так і при лаконічному. Однак енум не може розширюватися класи (тільки реалізовувати інтерфейси), тому вони не підходять для всіх випадків використання.

Еквівалентні візерунки в C++ і C#

У C++ Мейєр однотон (локальна статична ініціалізація) є різьбою-безпечною з C++11:

Singleton& getInstance() {
 static Singleton instance;
 return instance;
}

У C# класу передбачено вбудовану ініціацію з нижню-безпечну лінію:

public class Singleton {
 private static readonly Lazy<Singleton> _lazy =
 new Lazy<Singleton>(() => new Singleton());

 public static Singleton Instance => _lazy.Value;
}

Тестування та роздуми в інженерних додатках

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

  • Make однотони тестовані шляхом надання способу скидання екземпляра (наприклад, захищений метод використовується тільки в тестах) або шляхом введення залежності через інтерфейс. Багато сучасні програми не дозволяють однотонам аттогетер на користь залежностей, що управляють життєвий цикл.
  • Профілування перформансу] в режимі реального часу або високочастотних системах: вимірювати надголову синхронізації. У деяких випадках безфіксований однотон за допомогою (C#) або (C++) може бути виправданий.
  • Distributed systems вимагає однотонів, які мають унікальний процес, не по всьому процесу. Якщо вам потрібен кластерний однотон, скористайтеся зовнішнім координацією (наприклад, базою, ZooKeeper або обраним лідером).
  • Рефлекція та послідовність] може розбити синглтони. Використовуйте в Java послідовності та запобігати відображенню миттєвого миттєвого відкидання, викинувши виняток у конструкторі, якщо вже встановлений.

Висновок

Патерн Oneton залишається цінним інструментом в інструментальній скриньці програмного забезпечення, але його реалізація в багатопрочитаних середовищах вимагає суворої уваги до деталей. Розуміння і уникнення поширених помилок - наприклад, як непривабливі конструктори, відсутність синхронізації, неправильного використання волей, а також надсинхронізація -розробники можуть виробляти надійні, високопродуктивні однотони. Двосторонній запірний візерунок, статичний візерунок власника і енум-на основі однотонів на Java кожен пропонує міцний баланс безпеки і ефективності. У C++ і C# сучасні функції спрощують завдання далі.

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

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