1. Active object

Активний об'єкт (Active object) — це шаблон проєктування, який відокремлює потік виконання методу від потоку, у якому його викликано. Мета цього шаблону – забезпечувати паралельність виконання за рахунок використання асинхронних викликів методів та планувальника обробки запитів.

Спрощений варіант:

Active object

Класичний варіант:

Active object 2

Цей шаблон складається з шести елементів:

  • Об'єкт-заступник (proxy), який надає інтерфейс публічно доступних методів клієнта.
  • Інтерфейс, який визначає способи доступу до активного об'єкта.
  • Список запитів, що надходять від клієнтів.
  • Планувальник (scheduler), який визначає порядок виконання запитів.
  • Реалізація методів активного об'єкта.
  • Процедура зворотного виклику (callback) чи змінна (variable) для отримання клієнтом результату.

2. Lock

Паттерн Блокування (Lock) — механізм синхронізації, що дозволяє виключний доступ до розділеного ресурсу між кількома потоками. Блокування – це один із способів забезпечити політику управління розпаралелюванням.

Зазвичай використовується м'яке блокування. При цьому передбачається, що кожен потік намагається "захопити блокування" перед доступом до відповідного ресурсу, що розділяється.

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

Семафор – найпростіший тип блокування. З точки зору доступу до даних нема жодної різниці між режимами доступу: загальним (лише читання) або ексклюзивним (читання та запис). У режимі загального доступу кілька потоків можуть робити запит на блокування доступу до даних у режимі “лише читання”. Також використовується ексклюзивний режим доступу в алгоритмах оновлення та видалення.

Lock pattern

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

Спінлок – це блокування, яке очікує у циклі, доки не з'явиться доступ. Таке блокування дуже ефективне в разі, коли потік чекає на блокування незначного інтервалу часу: це дозволяє уникнути надлишкового перепланування потоків. Витрати на очікування доступу будуть значними при тривалому утриманні блокування одним із потоків.

Lock pattern 2

Для ефективної реалізації механізму блокування потрібна підтримка апаратного рівня. Її можна реалізувати у вигляді однієї або декількох атомарних операцій, як-от “test-and-set”, “fetch-and-add” або “compare-and-swap”. Такі інструкції дозволяють без переривань перевірити, чи блокування вільне, і якщо це так – зайняти блокування.

3. Monitor

Патерн Монітор — високорівневий механізм взаємодії та синхронізації процесів, що забезпечує доступ до ресурсів, які розділяються. Підхід до синхронізації двох або більше комп'ютерних завдань, що використовують спільний ресурс, зазвичай апаратуру або набір змінних.

При багатозадачності, що базується на моніторах, компілятор або інтерпретатор прозоро для програміста вставляє код блокування-розблокування в оформлені відповідним чином процедури, таким чином позбавляє програміста явного звернення до примітивів синхронізації.

Монітор складається з:

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

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

Саме так у Java працює оператор synchronized у парі з методами wait() та notify().

4. Double check locking

Блокування з подвійною перевіркою (Double checked locking) – паралельний шаблон проєктування, який призначається для зменшення накладних витрат, що пов'язані з отриманням блокування.

Спочатку перевіряється умова блокування без синхронізації. Потік намагається отримати блокування тільки якщо результат перевірки говорить про те, що отримання блокування необхідно.


//Double-Checked Locking
public final class Singleton {
private static Singleton instance; //Don't forget volatile modifier

public static Singleton getInstance() {
     if (instance == null) {                //Read

         synchronized (Singleton.class) {    //
             if (instance == null) {         //Read Write
                 instance = new Singleton(); //
             }
         }
     }
 }

Як створити об'єкт-сінглтон у потокобезпечному середовищі?


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

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


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

Такий підхід працюватиме, проте в нього є невеликий мінус. Після того, як об'єкт створено, щоразу при спробі його отримати в майбутньому буде виконуватися перевірка в блоці synchronized, отже буде лочитися поточний потік і все, що з цим пов'язано. Отже, цей код можна трохи оптимізувати:


public static Singleton getInstance() {
     if (instance != null)
        return instance;

    synchronized (Singleton.class) {
        if (instance == null)
        instance = new Singleton();
    }
}

Деякі мови та/або деякі машини не можуть безпечно реалізувати цей шаблон. Тому іноді його називають антипатерном. Через такі особливості з'явилися відносини строгого порядку “happens before” Java Memory Model і C++ Memory Model.

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

5. Scheduler

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

Більш детально з цим шаблоном можна ознайомитись у офіційній документації.