— Привіт, Аміго! У нас є панацея – ліки від усіх хвороб. Як ми вже встигли переконатися, неконтрольоване перемикання потоків – це проблема.

— А чому б потокам самим не вирішувати, коли перейти на наступний? Зробив усі важливі справи та «маякує»: я – все!

— Дозволяти потокам самим керувати своїм перемиканням – ще більша проблема. Раптом якийсь код не дуже красиво написаний, і потік ніколи сам не віддасть свого «процесорного часу». Давним-давно так і було – і це був тихий жах.

— Гаразд. І яке ж рішення є?

 Блокування потоків. І ось як це працює.

Було з'ясовано, що потоки заважають один одному, коли намагаються спільно працювати із спільними об'єктами та/або ресурсами. Як у прикладі з виведенням на консоль: консоль одна, а виводять на неї всі потоки. Непорядок.

Тому був вигаданий спеціальний об'єкт – м’ютекс. Це як табличка на дверях туалету: «вільно» або «зайнято». Він має два стани – об'єкт вільний і об'єкт зайнятий, або їх ще називають заблокований і розблокований.

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

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

— А як працювати з цим м’ютексом? Треба створювати спеціальні об'єкти?

— Усе набагато простіше. Розробники Java вбудували цей м’ютекс у клас Object. Тобі навіть створювати його не доведеться. Він є у кожного об'єкта. Ось як це все працює:

Код Опис
class MyClass
{
private String name1 = "Оля";
private String name2 = "Олена";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
Метод swap змінює місцями значення змінних name1 & name2.

Що буде, якщо його викликати з двох потокі одночасно?

Підсумковий порядок Код першого потоку Код другого потоку
String s1 = name1; //Оля
name1 = name2; //Олена
name2 = s1; //Оля

String s2 = name1; //Олена
name1 = name2; //Оля
name2 = s2; //Олена
String s1 = name1;
name1 = name2;
name2 = s1;
//виконується другий потік
//виконується другий потік
//виконується другий потік 
//потік чекає, коли звільниться м’ютекс
//потік чекає, коли звільниться м’ютекс
//потік чекає, коли звільниться м’ютекс
String s2 = name1;
name1 = name2;
name2 = s2;
Підсумок
Значення змінних двічі обмінялися місцями і повернулися на початкове місце.

Зверни увагу на ключево слово synchronized.

— Так, а що це означає?

— Коли один потік заходить всередину блока коду, позначеного словом synchronized, то Java-машина одразу блокує м’ютекс у об'єкта, який вказаний у круглих дужках після слова synchronized. Більше жоден потік не зможе зайти у цей блок, поки наш потік його не покине. Як тільки наш потік вийде з блоку, позначеного synchronized, то мютекс відразу автоматично розблокується і буде вільний для захоплення іншим потоком.

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

— Так просто і так вишукано. Гарне рішення.

— Так. А як ти вважаєш, що буде в цьому випадку?

Код Опис
class MyClass
{
private String name1 = "Оля";
private String name2 = "Олена";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public void swap2()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}
Методи swap і swap2 мають один і той же мютекс – об'єкт this.

Що буде, якщо один потік викликає метод swap, а інший – метод swap2?

— Оскільки мютекс у них один, то другому потоку доведеться чекати, поки перший потік вийде з блоку synchronized, тому проблем з одночасним доступом тут не буде.

— Молодець, Аміго! Правильне рішення!

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

Код Що відбувається насправді
class MyClass
{
private static String name1 = "Оля";
private static String name2 = "Олена";

public synchronized void swap()
{
String s = name1;
name1 = name2;
name2 = s;
}

public static synchronized void swap2()
{
String s = name1;
name1 = name2;
name2 = s;
}
}
class MyClass
{
private static String name1 = "Оля";
private static String name2 = "Олена";

public void swap()
{
synchronized (this)
{
String s = name1;
name1 = name2;
name2 = s;
}
}

public static void swap2()
{
synchronized (MyClass.class)
{
String s = name1;
name1 = name2;
name2 = s;
}
}
}