Перейти до вмісту

Освоюємо Java/Паралелізм

Матеріал з Вікіпідручника

Паралелізм в програмуванні (англ. Concurrency) — це створення програм у вигляді кількох виконуваних, паралельно-взаємодійних між собою гілок(частин) програми. Розрізняють процеси (processes) та нитки (threads). Частина перекладачів перекладають thread, як "потоки виконання", проте в ряді випадків цей переклад призводить до плутанини через існування потоків вводу/виводу (I/O Streams). Нитки є паралельно-виконуваними частинами процесу. Процеси — більш унезалежненні від інших процесів, ніж нитки від інших ниток. Зокрема, кожен процес має власний адресний простір у пам'яті комп'ютера, нитки ж одного процесу використовують спільний адресний простір пам'яті.

У програмуванні на Java, програмістам зазвичай, приходиться мати справу із окремими нитками. Багатонитковість (multithreading) є важливою можливістю Java. Кожна програма Java має хоча б одну нитку виконання. Виконання програми розпочинається із створенням головної нитки (main thread).

При роботі з нитками, програміст може або в ручну керувати ними, або ж використовувати спеціальний менеджер виконувач (executor). Спочатку, необхідно розглянути перший спосіб управління нитками виконання.

Створення ниток виконання

[ред.]

Кожна нитка це екземпляр класу Thread. Щоб мати змогу створити нову нитку та оперувати нею, зрозуміло, що необхідний код для виконання у цій нитці. Це можна зробити двома шляхами:

  • через реалізацію (implements) інтерфейсу Runnable, що робиться через визначення методу run:
public class HelloRunnable implements Runnable {

    public void run() {
        System.out.println("Привіт - це нитка!");
    }

    public static void main(String args[]) {
        (new Thread(new HelloRunnable())).start();
    }
}
  • другий спосіб полягає у створенні підкласу для Thread. Клас Thread уже містить реалізацію інтерфейсу Runnable. Проте його реалізація методу run — порожня. Тож програміст у своєму коді просто заміщає метод run:
public class HelloThread extends Thread {

    public void run() {
        System.out.println("Привіт - це нитка!");
    }

    public static void main(String args[]) {
        (new HelloThread()).start();
    }
}

Як бачимо в обох випадках для створення нової нитки викликається метод Thread.start.

Більш кращим для застосування є перший спосіб, оскільки він дозволяє нам розширити ще якийсь клас у нашому класі (У Java відсутнє множинне успадкування, тож не можливо розширити декілька класів).

Призупинення та переривання нитки

[ред.]

Виконання нитки можна призупинити за допомогою методу sleep класу Thread. Можливо, також, перервати виконання нитки з іншої нитки за допомогою методу interrupt. Скориставшись методом join, ми можемо задати час очікування на завершення виконання іншої нитки (безкінечно або вказаний час). Наступний приклад демонструє роботу двох ниток. Основна(main) нитка запускає виконання іншої нитки, яка просто виводить важливе повідомлення по одному рядку через кожні 4 секунди. Основна нитка виконання кожної секунди виводить повідомлення, що вона все ще очікує на завершення породженої нитки. Якщо виконання породженої нитки триває більше періоду часу, що заданий у змінній patience(терпіння), то нитка перериває виконання породженої нитки (закінчується терпіння). При перериванні породженої нитки, у ній виникає виняток типу InterruptedException, який обробляється нею за допомогою конструкції try-catch.

package ua.wikibooks.oj;

    public class ThreadInteruption {

        // Display a message, preceded by
        // the name of the current thread
        static void threadMessage(String message) {
            String threadName =
                Thread.currentThread().getName();
            System.out.format("%s: %s%n",
                              threadName,
                              message);
        }

        private static class MessageLoop
            implements Runnable {
            public void run() {
                String importantInfo[] = {  
 
                    "У діброві - дуби,",
                    "Під дубами - гриби,",
                    "Трава - між грибами,",
                    "Хмарки - над дубами."
                };
                try {
                    for (int i = 0;
                         i < importantInfo.length;
                         i++) {
                        // Пауза на 4 секунди
                        Thread.sleep(4000);
                        // Надруквати повідомлення
                        threadMessage(importantInfo[i]);
                    }
                } catch (InterruptedException e) {
                    threadMessage("Виконання задачі не завершене!");
                }
            }
        }

        public static void main(String args[])
            throws InterruptedException {

            // Пауза, у мілісекундах
            // перед тим як буде перервано MessageLoop
            // thread (по замовчуванню одна година).
            long patience = 1000 * 60 * 60;

            // Якщо наявний аргумент в командному рядку, 
            // задати терпіння (patience) в секундах
            if (args.length > 0) {
                try {
                    patience = Long.parseLong(args[0]) * 1000;
                } catch (NumberFormatException e) {
                    System.err.println("Аргумент повинен бути цілим числом.");
                    System.exit(1);
                }
            }

            threadMessage("Старт MessageLoop thread");
            long startTime = System.currentTimeMillis();
            Thread t = new Thread(new MessageLoop());
            t.start();

            threadMessage("Чекаю допоки MessageLoop thread не закінчить");
            // loop until MessageLoop
            // thread exits
            while (t.isAlive()) {
                threadMessage("Все ще очікую...");
                
                // Почекати 1 секунду
                // на закінчення MessageLoop thread
                t.join(1000);
                if (((System.currentTimeMillis() - startTime) > patience)
                      && t.isAlive()) {
                    threadMessage("Терпіння закінчилось, більше не чекатиму!");
                    // перервати виконання нитки MessageLoop 
                    t.interrupt();
                    // Може зайняти певний час
                    // -- безкінечне очікування
                    t.join();
                }
            }
            threadMessage("Кінець!");
        }
    }

При виконанні коду, без задання аргументу, скоріш за все нитка виконання MessageLoop успішно завершить свою роботу, а за нею вже і основна нитка. Якщо ж змінити значення змінної patience на менше, або через аргумент, або прямо в коді, то ми отримаємо перед завершенння програми повідомлення "Виконання задачі не завершене!".

Ось результат з успішним завершення виконання MessageLoop:

main: Старт MessageLoop thread
main: Чекаю допоки MessageLoop thread не закінчить
main: Все ще очікую...
main: Все ще очікую...
main: Все ще очікую...
main: Все ще очікую...
main: Все ще очікую...
Thread-0: У діброві - дуби,
main: Все ще очікую...
main: Все ще очікую...
main: Все ще очікую...
Thread-0: Під дубами - гриби,
main: Все ще очікую...
main: Все ще очікую...
main: Все ще очікую...
main: Все ще очікую...
main: Все ще очікую...
Thread-0: Трава - між грибами,
main: Все ще очікую...
main: Все ще очікую...
main: Все ще очікую...
main: Все ще очікую...
Thread-0: Хмарки - над дубами.
main: Кінець!

Інтерференція ниток

[ред.]

Синхронізація

[ред.]

(Ще не написано)

Джерела інформації

[ред.]

(Даний розділ незавершений і потребує доповнення)

Узагальнення · Лямбда-вирази