Вступ
Багатопоточність у Java була закладена з перших днів. Тому давайте коротко ознайомимось із тим, про що це — багатопоточність.
Код нашого Hello World додатку трохи змінимо на наступний:
class HelloWorldApp {
public static void main(String[] args) {
System.out.println("Hello, " + args[0]);
}
}
args — це масив вхідних параметрів, переданих під час запуску програми. Збережемо цей код у файл з іменем, яке збігається з іменем класу і з розширенням
.java. Скомпілюємо за допомогою утиліти
javac:
javac HelloWorldApp.java
Після цього викличемо наш код із якимось параметром, наприклад, Roger:
java HelloWorldApp Roger
![Thread'ом Java не зіпсуєш: Частина I — потоки - 2]()
У нашого коду зараз є серйозний недолік. Якщо не передати жодного аргументу (тобто виконати просто java HelloWorldApp), ми отримаємо помилку:
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 0
at HelloWorldApp.main(HelloWorldApp.java:3)
Виникла виключна ситуація (тобто помилка) у thread (потоці) з ім’ям
main.
Виходить, у Java є якісь потоки? Звідси починається наш шлях.
Java і потоки
Щоб зрозуміти, що таке потік, треба зрозуміти, як відбувається запуск Java додатку.
Давайте змінимо наш код таким чином:
class HelloWorldApp {
public static void main(String[] args) {
while (true) {
}
}
}
Тепер давайте скомпілюємо це знову за допомогою javac.
Далі для зручності запустимо наш Java код у окремому вікні. У Windows це можна зробити так:
start java HelloWorldApp.
Тепер за допомогою утиліти
jps подивимося, яку інформацію нам повідомить Java:
![Thread'ом Java не зіпсуєш: Частина I — потоки - 3]()
Перше число — це PID або Process ID, ідентифікатор процесу. Що таке процес?
Процес — це сукупність коду та даних, які розділяють загальний віртуальний адресний простір.
За допомогою процесів виконання різних програм ізольовано одне від одного: кожен додаток використовує свою область пам’яті, не заважаючи іншим програмам. Докладніше радимо ознайомитися в статті: "
https://habr.com/post/164487/".
Процес не може існувати без потоків, тому якщо існує процес, у ньому існує хоча б один потік.
Як же це відбувається у Java? Коли ми запускаємо Java програму, її виконання починається з методу
main. Ми ніби входимо у програму, тому цей особливий метод
main називається точкою входу, або "entry point". Метод
main завжди має бути
public static void, щоб віртуальна машина Java (JVM) змогла почати виконання нашої програми. Докладніше див. "
Why is the Java main method static?".
Виходить, що java launcher (java.exe або javaw.exe) — це простий додаток (simple C application): він завантажує різні DLL, які насправді є JVM.
Java launcher виконує певний набір Java Native Interface (JNI) викликів. JNI — це механізм, який з’єднує світ віртуальної машини Java і світ C++. Виходить, що launcher — це не JVM, а її завантажувач. Він знає, які правильні команди потрібно виконати, щоб запустилася JVM. Знає, як організувати все необхідне оточення за допомогою JNI викликів.
У цю організацію оточення входить і створення головного потоку, який зазвичай називається
main.
Щоб наочніше розглянути, які живуть потоки в java процесі, використаємо програму
jvisualvm, яка входить у поставку JDK.
Знаючи pid процесу, ми можемо відкрити дані одразу по ньому:
jvisualvm --openpid айдіпроцесу
![Thread'ом Java не зіпсуєш: Частина I — потоки - 4]()
Цікаво, що кожен потік має свою окрему область у пам’яті, виділеній для процесу. Цю структуру пам’яті називають стеком.
Стек складається з фреймів. Фрейм — це точка виклику методу, execution point. Також фрейм може бути представлений як StackTraceElement (див. Java API для
StackTraceElement).
Детальніше про пам’ять, виділену кожному потоку, можна прочитати
тут.
Якщо подивитись на
Java API і пошукати там слово Thread, ми побачимо, що є клас
java.lang.Thread.
Саме цей клас представляє у Java потік, і з ним нам і доведеться працювати.
![Thread'ом Java не зіпсуєш: Частина I — потоки - 5]()