JavaRush /Java блог /Random UA /У чому різниця між м'ютексом, монітором та семафором

У чому різниця між м'ютексом, монітором та семафором

Стаття з групи Random UA
Вітання! Вивчаючи багатопоточність на JavaRush, ти часто зустрічав поняття "м'ютекс" та "монітор". Чи зможеш зараз, без підгляду відповісти, чим вони відрізняються? :) У чому різниця між м'ютексом, монітором та семафором?Якщо зміг - молодець! Якщо ж ні (а найчастіше так і буває) – не дивно. Поняття «м'ютекс» та «монітор» справді пов'язані між собою. Більше того, читаючи лекції та дивлячись відео по багатопоточності на зовнішніх ресурсах в Інтернеті, ти зіткнешся з ще одним схожим поняттям – «семафор». Його функціонал теж багато в чому схожий на монітор і м'ютекс. Тому розберемося з цими трьома термінами, розглянемо кілька прикладів і остаточно впорядкуємо в голові розуміння того, чим вони один від одного відрізняються :)

М'ютекс

М'ютекс – це спеціальний об'єкт для синхронізації потоків. Він «прикріплений» до кожного об'єкта в Java - це ти вже знаєш :) Неважливо, чи користуєшся ти стандартними класами або створив власні класи, скажімо, Catі Dog: у всіх об'єктів всіх класів є м'ютекс . Назва "м'ютекс" походить від англійського "MUTual EXclusion" - "взаємний виняток", і це відмінно відображає його призначення. Як ми й говорабо в одній із минулих лекцій, завдання м'ютексу — забезпечити такий механізм, щоб доступ до об'єкта в певний час був лише в одного потоку.. Популярною аналогією мьютексу в реальному житті можна вважати приклад з туалетом. Коли людина заходить у туалет, вона зачиняє зсередини двері на замок. Туалет виконує роль об'єкта, доступ якого отримують кілька потоків. Замок на дверях туалету – роль м'ютексу, а черга з людей зовні – роль потоків. Замок на дверях - м'ютекс туалету: він гарантує, що всередині одночасно може бути лише одна людина. У чому різниця між м'ютексом, монітором та семафором - 2Іншими словами, лише один потік у певний час може працювати із загальними ресурсами. Спроби інших потоків (людей) отримати доступ до зайнятих ресурсів будуть невдалими. У м'ютексу є кілька важливих особливостей. По-перше , можливі лише два стани — «вільний» і «зайнятий». Це спрощує розуміння принципу роботи: можна провести паралелі з булевими зміннимиtrue/false або двійковою системою числення 1/0. По-друге , станами не можна керувати безпосередньо. У Java немає механізмів, які б явно взяти об'єкт, отримати його м'ютекс і привласнити йому потрібний статус. Іншими словами, ти не можеш зробити щось на кшталт:
Object myObject = new Object();
Mutex mutex = myObject.getMutex();
mutex.free();
Таким чином, звільнити м'ютекс об'єкта не можна. Прямий доступ до нього є лише Java-машини. Програмісти працюють з м'ютексами за допомогою засобів мови.

Монітор

Монітор — це додаткова надбудова над м'ютексом. Фактично монітор - це "невидимий" для програміста шматок коду . Говорячи про м'ютекс раніше, ми наводабо простий приклад:
public class Main {

   private Object obj = new Object();

   public void doSomething() {

       //...якась логіка, доступна всім потоків

       synchronized (obj) {

           // Логіка, яка одночасно доступна тільки для одного потоку
       }
   }
}
У блоці коду, який позначений словом synchronized, відбувається захоплення м'ютексу нашого об'єкта obj. Добре, захоплення відбувається, але як саме забезпечується «захисний механізм»? Чому побачивши слова synchronizedінші потоки не можуть пройти всередину блоку? Захисний механізм створює саме монітор! Компілятор перетворює слово synchronizedна кілька спеціальних шматків коду. Ще раз повернемося до нашого прикладу з методом doSomething()та доповнимо його:
public class Main {

   private Object obj = new Object();

   public void doSomething() {

       //...якась логіка, доступна всім потоків

       // Логіка, яка одночасно доступна тільки для одного потоку
       synchronized (obj) {

           /*выполнить важную работу, при которой доступ к об'єкту
           должен быть только у одного потока*/
           obj.someImportantMethod();
       }
   }
}
Ось що відбуватиметься «під капотом» нашої програми після того, як компілятор перетворює цей код:
public class Main {

   private Object obj = new Object();

   public void doSomething() throws InterruptedException {

       //...якась логіка, доступна всім потоків

       //логика, которая одновременно доступна только для одного потока:

       /*до тех пор, пока мьютекс об'єкта занят -
       любой другой поток (кроме того, который его захватил), спит*/
       while (obj.getMutex().isBusy()) {
           Thread.sleep(1);
       }

       //пометить мьютекс об'єкта як занятый
       obj.getMutex().isBusy() = true;

       /*выполнить важную работу, при которой доступ к об'єкту
       должен быть только у одного потока*/
       obj.someImportantMethod();

       //освободить мьютекс об'єкта
       obj.getMutex().isBusy() = false;
   }
}
Приклад, звісно, ​​несправжній. Тут ми за допомогою Java-подібного коду спробували відобразити те, що відбувається у цей момент усередині Java-машини. Однак цей псевдокод дає чудове розуміння того, що насправді відбувається з об'єктом і потоками всередині блоку synchronizedі як компілятор перетворює це слово на кілька «невидимих» для програміста команд. Насправді, монітор в Java виражений за допомогою словаsynchronized . Весь код, який з'явився замість слова synchronizedв останньому прикладі, — це монітор.

Семафор

Ще одне слово, з яким ти стикаєшся при самостійному вивченні багатопотоковості - "семафор". Давай розберемося що це таке, і чим він відрізняється від монітора та м'ютексу. Семафор – це засіб для синхронізації доступу до якогось ресурсу. Його особливість у тому, що з створенні механізму синхронізації він використовує лічильник. Лічильник показує, скільки потоків одночасно можуть отримувати доступ до спільного ресурсу. У чому різниця між м'ютексом, монітором та семафором - 3Семафори в Java представлені класом Semaphore. Під час створення об'єктів-семафорів ми можемо використовувати такі конструктори:
Semaphore(int permits)
Semaphore(int permits, boolean fair)
У конструктор ми передаємо:
  • int permits- Початкове і максимальне значення лічильника. Тобто, скільки потоків одночасно можуть мати доступ до загального ресурсу;

  • boolean fair— для встановлення порядку, де потоки отримуватимуть доступ. Якщо fair= true , доступ надається очікуваним потокам у тому порядку, у якому вони запитували. Якщо він дорівнює false , порядок визначатиме планувальник потоків.

Класичний приклад використання семафорів - завдання про філософів, що обідають .
У чому різниця між м'ютексом, монітором та семафором - 4
Ми трохи спростимо її умови для кращого розуміння. Уяви, що ми маємо 5 філософів, яким треба пообідати. При цьому ми маємо один стіл, і одночасно перебувати за ним можуть не більше двох осіб. Наше завдання – нагодувати всіх філософів. Ніхто з них не повинен залишитися голодним, і при цьому вони не повинні заблокувати один одного при спробі сісти за стіл (ми повинні уникнути deadlock). Ось як виглядатиме наш клас філософа:
class Philosopher extends Thread {

   private Semaphore sem;

   // поел ли философ
   private boolean full = false;

   private String name;

   Philosopher(Semaphore sem, String name) {
       this.sem=sem;
       this.name=name;
   }

   public void run()
   {
       try
       {
           // если философ еще не ел
           if (!full) {
               //Запрашиваем у семафора разрешение на выполнение
               sem.acquire();
               System.out.println (name + " садится за стол");

               // философ ест
               sleep(300);
               full = true;

               System.out.println (name + " поел! Он выходит из-за стола");
               sem.release();

               // философ ушел, освободив место другим
               sleep(300);
           }
       }
       catch(InterruptedException e) {
           System.out.println ("Що-то пошло не так!");
       }
   }
}
А ось код для запуску нашої програми:
public class Main {

   public static void main(String[] args) {

       Semaphore sem = new Semaphore(2);
       new Philosopher(sem,"Сократ").start();
       new Philosopher(sem,"Платон").start();
       new Philosopher(sem,"Аристотель").start();
       new Philosopher(sem,"Фалес").start();
       new Philosopher(sem,"Пифагор").start();
   }
}
Ми створабо семафор з лічильником 2, щоб відповідати умові: одночасно їсти можуть лише два філософи. Тобто одночасно працювати можуть лише два потоки, адже наш клас Philosopherуспадкований від Thread! Методи acquire()та release()класу Semaphoreуправляють його лічильником дозволів. Метод acquire()запитує дозвіл доступу до ресурсу у семафора. Якщо лічильник > 0, дозвіл надається, а лічильник зменшується на 1. Метод release()«звільняє» виданий раніше дозвіл і повертає його до лічильника (збільшує лічильник дозволів семафору на 1). Що ж у нас вийде під час запуску програми? Чи вирішено завдання, чи не переберуться наші філософи, чекаючи своєї черги? :) Ось який висновок у консоль ми отримали: Сократ сідає за стіл Платон сідає за стіл Сократ поїв! Він виходить із-за столу Платон поїв! Він виходить з-за столу Арістотель сідає за стіл Піфагор сідає за стіл Арістотель поїв! Він виходить із-за столу Піфагор поїв! Він виходить з-за столу Фалес сідає за стіл Фалес поїв! Він виходить із-за столу У нас все вийшло! І хоча Фалесу довелося обідати поодинці, думаю, він на нас не в образі :) Ти міг помітити деяку подібність між м'ютексом та семафором. У них загалом однакове призначення: синхронізувати доступ до якогось ресурсу. У чому різниця між м'ютексом, монітором та семафором - 5Різниця тільки в тому, що м'ютекс об'єкта може захопити одночасно лише один потік, а у випадку з семафором використовується лічильник потоків, і доступ до ресурсу можуть отримати відразу кілька з них. І це не просто випадкова схожість :) Насправді м'ютекс - це одномісний семафор . Тобто це семафор, лічильник якого спочатку встановлено у значенні 1. Його ще називають «двійковим семафором», оскільки його лічильник може мати лише 2 значення — 1 («вільно») та 0 («зайнято»). От і все! Як бачиш, все виявилося не таким вже й заплутаним :) Тепер, якщо ти захочеш вивчити тему багатопоточності докладніше в Інтернеті, тобі буде легше орієнтуватися в поняттях. До зустрічі на наступних уроках!
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ