— Привіт, Аміго! Ми вже обговорили переваги та зручності, які несе з собою багатопотоковість (multithreading). Тепер час подивитись і на недоліки. Вони, на жаль, відчутні.

Раніше ми дивились на програму як на набір об'єктів, що викликають методи один одного. Тепер все стало трохи складніше. Програма – це радше набір об'єктів, якими нишпорять кілька «маленьких роботів» – потоків – і виконують команди, які є у методах.

Формально – друге не скасовує перше. Це все ще об'єкти, і вони все ще викликають методи один в одного. Але треба не забувати, що потоків – кілька, і кожен потік виконує свою роботу – своє завдання.

Програма стає складнішою. Різні потоки змінюють стан різних об'єктів відповідно до завдання, яке кожен з них виконує. І можуть заважати один одному.

Але найстрашніше відбувається глибоко всередині Java-машини. Як я вже розповідала, видима одночасність роботи потоків досягається завдяки тому, що процесор постійно перемикається з одного потока на інший. Переключився на потік, попрацював 10 мілісекунд, переключився на наступний потік, там попрацював 10 мілісекунд тощо. І тут виникає проблема: перемикання може відбутися в невдалий момент. Приклад:

Код першого потоку Код другого потоку
System.out.print ("Миколі");
System.out.print (" ");
System.out.print ("15");
System.out.print (" ");
System.out.print ("років");
System.out.println ();
System.out.print ("Олені");
System.out.print (" ");
System.out.print ("21");
System.out.print (" ");
System.out.print ("рік");
System.out.println ();
Очікуване виведення в консолі
Миколі 15 років
Олені 21 рік
Підсумковий порядок Код першого потоку Код другого потоку
System.out.print ("Миколі");
System.out.print ("Олені");
System.out.print (" ");
System.out.print (" ");
System.out.print ("15");
System.out.print ("21");
System.out.print (" ");
System.out.print (" ");
System.out.print ("років");
System.out.println ();
System.out.print ("рік");
System.out.println ();
System.out.print ("Миколі");
//виконується другий потік
//виконується другий потік
System.out.print (" ");
System.out.print ("15");
//виконується другий потік
//виконується другий потік
System.out.print (" ");
System.out.print ("років");
System.out.println ();
//виконується другий потік
//виконується другий потік
//виконується другий потік
System.out.print ("Олені");
System.out.print (" ");
//виконується другий потік
//виконується другий потік
System.out.print ("21");
System.out.print (" ");
//виконується другий потік
//виконується другий потік
//виконується другий потік
System.out.print ("рік");
System.out.println ();
Реальне виведення в консолі
Миколі Олені  15 21 років
рік

Або ось ще приклад:

Код Опис
class MyClass
{
private String name1 = "Оля";
private String name2 = "Олена";
public void swap()
{
String s = name1;
name1 = name2;
name2 = s;
}
}
Метод swap міняє місцями значення змінних name1 & name2.

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

Підсумковий порядок Код першого потоку Код другого потоку
String s1 = name1; //Оля
name1 = name2; //Олена
String s2 = name1; //Олена(!)
name1 = name2; //Олена
name2 = s1; //Оля
name2 = s2; //Олена
String s1 = name1;
name1 = name2;
//виконується другий потік
//виконується другий потік
name2 = s1;
//виконується другий потік
//виконується другий потік
//виконується другий потік
String s2 = name1;
name1 = name2;
//виконується другий потік
name2 = s2;
Підсумок
Обидві змінні мають значення «Олена».
Об'єкт «Оля» було перезатерто і втрачено.

— Хто б міг подумати, що за елементарного присвоєння можливі такі помилки?

— Так, але для цієї проблеми є рішення. Але про це трохи пізніше – у мене горло пересохло.