Передмова. Дядя Петя
Отже, припустимо, що ми захотіли набрати пляшку води. В наявності є пляшка та кран з водою дядька Петі. Дяді Пете сьогодні встановабо новий кран, і він без хвилювання нахвалював його красу. До цього він користувався тільки старим краном, що засмітився, тому черги на розливі були колосальні. Трохи повозившись, з боку розливу почувся звук води, що набирається, через 2 хвабони пляшка все ще знаходиться в стадії наповнення, за нами зібралася звична черга, а в голові малюється образ того, як дбайливий дядько Петя відбирає тільки кращі молекули H2O в нашу пляшку. Навчений життям дядько Петя заспокоює особливо агресивних і обіцяє закінчити якнайшвидше. Покінчивши з пляшкою, він бере наступну і включає звичний натиск, який не розкриває всіх можливостей нового крана. Люди незадоволені.
Теорія
Багатопотоковість - це властивість платформи створювати кілька потоків у рамках одного процесу. Створення та виконання потоку набагато простіше, ніж створення процесу, тому при необхідності реалізувати кілька паралельних дій в одній програмі використовуються додаткові потоки. У JVM будь-яка програма запускається переважно потоці, а вже з нього запускаються інші. У межах процесу потоки здатні обмінюватися даними між собою. При запуску нового потоку його можна оголосити як користувальницький за допомогою методу
setDaemon(true);
такі потоки автоматично завершаться, якщо не залишиться інших працюючих потоків. Потоки мають пріоритет роботи (вибір пріоритету не гарантує, що потік із вищим пріоритетом завершиться швидше за поток з нижчим пріоритетом).
- MIN_PRIORITY
- NORM_PRIORITY (default)
- MAX_PRIORITY
Основні методи під час роботи з потоками:
run()
- Виконує потік
start()
- Запускає потік
getName()
- Повертає ім'я потоку
setName()
- Задає ім'я потоку
wait()
– успадкований метод, потік очікує виклику методу notify()
з іншого потоку
notify()
– успадкований метод, відновлює раніше зупинений потік
notifyAll()
– успадкований метод, відновлює раніше зупинені потоки
sleep()
- Зупиняє потік на заданий час
join()
- Чекає завершення потоку
interrupt()
– перериває виконання потоку
Більше методів можна знайти
тут Саме час задуматися про нові потоки, якщо у вашій програмі є:
- Доступ до мережі
- Доступ до файлової системи
- GUI
Клас Thread
Потоки в Java представлені у вигляді класу
Thread
та його спадкоємців. Наведений нижче приклад є найпростішою реалізацією потокового класу.
import static java.lang.System.out;
public class ExampleThread extends Thread{
public static void main(String[] args) {
out.println("Основной поток");
new ExampleThread().start();
}
@Override
public void run() {
out.println("Новый поток");
}
}
В результаті отримаємо
Основной поток
Новый поток
Тут ми створюємо наш клас і робимо його спадкоємцем класу
Thread
, після чого пишемо метод main() для запуску основного потоку та перевизначаємо метод
run()
класу
Thread
. Тепер створивши екземпляр нашого класу і виконавши його успадкований метод,
start()
ми запустимо новий потік, в якому виконається все, що описано в тілі методу
run()
. Звучить складно, але глянувши на код прикладу, все має бути зрозуміло.
Інтерфейс Runnable
Oracle також пропонує для запуску нового потоку реалізовувати інтерфейс
Runnable
, що дає нам більшу гнучкість у розробці, ніж єдине доступне успадкування у попередньому прикладі (якщо зазирнути у вихідні класи
Thread
можна побачити, що він також реалізує інтерфейс
Runnable
). Застосуємо метод створення нового потоку, що рекомендується.
import static java.lang.System.out;
public class ExampleRunnable implements Runnable {
public static void main(String[] args) {
out.println("Основной поток");
new Thread(new ExampleRunnable()).start();
}
@Override
public void run() {
out.println("Новый поток");
}
}
В результаті отримаємо
Основной поток
Новый поток
Приклади дуже подібні, т.к. при написанні коду нам довелося реалізувати абстрактний метод
run()
, описаний в інтерфейсі
Runnable
. Запуск нового потоку трохи відрізняється. Ми створабо екземпляр класу
Thread
, передавши як параметр посилання на екземпляр нашої реалізації інтерфейсу
Runnable
. Саме такий підхід дозволяє створювати нові потоки без прямого спадкування класу
Thread
.
Довгі операції
Наступний приклад наочно покаже переваги використання кількох потоків. Припустимо, у нас є просте завдання, що вимагає кількох тривалих обчислень, до цієї статті ми вирішували б її в методі,
main()
можливо, розбивши для зручності сприйняття на окремі методи, можливо навіть класи, але суть була б одна. Усі операції відбувалися б послідовно одна одною. Давайте змоделюємо великовагові обчислення та заміряємо час їх виконання.
public class ComputeClass {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 1");
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 2");
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 3");
long timeSpent = System.currentTimeMillis() - startTime;
System.out.println("программа выполнялась " + timeSpent + " миллисекунд");
}
}
В результаті отримаємо
complete 1
complete 2
complete 3
программа выполнялась 9885 миллисекунд
Час виконання залишає бажати кращого, а ми весь цей час дивимося на порожній екран виводу, і ситуація дуже скидається на історію про дядька Петю тільки тепер у його ролі ми, розробники, які не скористалися всіма можливостями сучасних пристроїв. Виправлятимемося.
public class ComputeClass {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
new MyThread(1).start();
new MyThread(2).start();
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 3");
long timeSpent = System.currentTimeMillis() - startTime;
System.out.println("программа выполнялась " + timeSpent + " миллисекунд");
}
}
class MyThread extends Thread{
int n;
MyThread(int n){
this.n = n;
}
@Override
public void run() {
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete " + n);
}
}
В результаті отримаємо
complete 1
complete 2
complete 3
программа выполнялась 3466 миллисекунд
Час роботи значно скоротилося (цей ефект може бути не досягнутий або взагалі збільшити час виконання на процесорах, що не підтримують багатопоточність). Варто зауважити, що потоки можуть завершитися не по порядку, і якщо розробнику необхідна передбачуваність дій, він повинен реалізувати її самостійно під конкретний випадок.
Групи потоків
Потоки Java можна об'єднувати в групи, для цього використовується клас
ThreadGroup
. До складу груп можуть входити як одиночні потоки, і цілі групи. Це може виявитися зручним, якщо вам треба перервати пов'язані потоки, наприклад, з мережею при обриві з'єднання. Докладніше про групи можна почитати
тут Сподіваюся, тепер тема стала вам більш зрозумілою і ваші користувачі будуть задоволені.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ