Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лекція Введення в потоки Java

.docx
Скачиваний:
8
Добавлен:
08.10.2015
Размер:
41.64 Кб
Скачать

Введення в потоки Java

Опис: Засоби й API підтримки потоків мови Java оманливе прості; однак цього не скажеш про процес написання складних програм, ефективно використовують потоки. Це навчальний посібник розповідає про основи потокового виконання: що являють собою потоки, в чому їх користь і як почати писати прості програми, які їх використовують. Крім того, ви дізнаєтеся про обмін даними між потоками, про управління потоками і про те, як потоки можуть взаємодіяти один з одним.

Основи потоків

Що таке потоки?

Практично всі операційні системи підтримують концепцію процесів - незалежно працюючих програм, до деякої міри ізольованих один від одного.

Потоками називаються засоби, що дозволяють кількох операцій співіснувати в рамках одного процесу. Потоки підтримуються більшістю сучасних операційних систем, і ця концепція існує в різних формах вже довгі роки. Java є першим масовим мовою програмування, який явно включає до свого складу потоки, а не розглядає їх як функцію нижележащей операційної системи.

Іноді потоки називають «полегшеними» процесами. Подібно процесам, потоки являють собою незалежні паралельні шляхи виконання програм; при цьому кожен потік володіє власними стеком, лічильником команд і локальними змінними. Проте потоки всередині процесу менш ізольовані один від одного, ніж окремі процеси. Вони використовують спільну пам'ять покажчики файлів та інші стани.

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

Засоби підтримки і API потоків мови Java оманливе прості; однак цього не скажеш про написання складних програм, ефективно використовують потоки. Оскільки кілька потоків співіснують в одній області пам'яті і використовують загальні змінні, потрібно стежити за тим, щоб потоки не заважали один одному.

Всі програми Java користуються потоками

Будь-яка програма Java використовує хоча б один потік - головний потік. При запуску програми Java JVM створює головний потік і викликає метод main () усередині цього потоку.

Крім того, JVM створює інші потоки, які здебільшого, вам непомітні - наприклад, потоки, пов'язані зі збором сміття, фіналізацією об'єктів та іншими службовими операціями в JVM. Потоки можуть створюватися й іншими інструментами, такими як інструменти інтерфейсу користувача AWT (інструменти для управління абстрактними вікнами) або Swing, контейнери сервлетов, сервери додатків і RMI (видалені виклики методів).

Навіщо потрібні потоки?

Існує безліч аргументів на користь застосування потоків у програмах Java. Якщо ви використовуєте Swing, сервлети, RMI або технологію Enterprise JavaBeans (EJB), ви, ймовірно, вже використовуєте потоки, хоча й не підозрюєте про це.

Потоки слід використовувати вже тому, що вони допомагають:

Підвищити чуйність інтерфейсу користувача

Використовувати переваги багатопроцесорних систем

Спростити моделювання

Виконувати асинхронну або фонову обробку

Більш чуйний інтерфейс користувача (UI)

Керовані подіями UI-інструментарії, наприклад, AWT і Swing, містять потік, який обробляє такі події для користувача інтерфейсу, як натискання клавіш і клацання мишею.

Програми AWT і Swing прикріплюють до об'єктів користувальницького інтерфейсу приймачі подій. Ці приймачі повідомляються про звершення деякого специфічного події, такого як клацання кнопкою миші. Приймачі подій викликаються з потоку обробки подій AWT.

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

Щоб не допустити зависання користувацького інтерфейсу, приймач подій повинен передоручати тривалі завдання іншому потоку, щоб потік AWT міг продовжити обробку подій користувальницького інтерфейсу під час виконання завдання (включаючи запити на припинення її виконання).

Використання переваг багатопроцесорних систем

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

Сучасні операційні системи, включаючи Linux, Solaris і Windows NT / 2000, можуть використовувати переваги наявності декількох процесорів і передавати виконання потоків будь-якого доступного процесору.

Базовим блоком планувальника, як правило, є потік; якщо програма має лише один активний потік, то в кожен момент часу вона може працювати тільки на одному процесорі. Якщо програма має кілька активних потоків, то вони можуть виконуватися одночасно. В грамотно написаної програмою застосування декількох потоків може підвищити пропускну спроможність і продуктивність.

Простота моделювання

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

Іншим прикладом спрощення програми при використанні окремих потоків є додаток з декількома незалежними компонентами, які управляються подіями. Наприклад, додаток може містити компонент, який підраховує секунди, що минули після деякої події, і оновлює зображення на екрані. Замість того щоб включати в основний цикл періодичну перевірку часу і оновлювати зображення, значно простіше (і безпомилковіше) завести потік, який нічого не робить, а просто певний час очікує і потім оновлює лічильник на екрані. При цьому головному потоку взагалі не потрібно піклуватися про таймері.

Асинхронна або фонова обробка

Серверні додатки отримують вхідні дані від віддалених джерел, таких як сокети. Якщо ви виконуєте читання сокета, дані в якому відсутні, виклик SocketInputStream.read () призведе до блокування до моменту появи даних.

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

Якщо замість цього ви створите потік, який читає сокет, головний потік буде виконувати інші завдання, в той час як цей потік буде чекати надходження даних з сокета. Можна навіть створити кілька потоків, щоб одночасно виконувати читання з декількох сокетов. У цьому випадку ви швидко отримуєте сповіщення про появу даних (так як при цьому прокидається чекає потік), не вдаючись до частих опитуваннями для перевірки наявності даних. До того ж код, що очікує появи даних в сокеті, значно простіше і менш схильний до помилок, ніж код опитування.

Потоки прості, але часом небезпечні

Хоча потоки Java дуже прості у використанні, існує кілька підводних каменів, яких слід уникати при створенні багатопоточних програм.

Якщо декілька потоків звертаються до одного і того ж об'єкту даних, такому як статичне поле, екземпляр поля глобально доступного об'єкта або загальна колекція, вони повинні координувати свій доступ до даних, так щоб кожен з них бачив узгоджене подання даних і не потрапляв під зміни, що вносяться іншим потоком. Для цього в мові Java передбачені два ключових слова: synchronized і volatile. Застосування і значення цих ключових слів ми вивчимо в цьому навчальному посібнику пізніше.

Звертаючись до змінних з декількох потоків, ви повинні забезпечити відповідну синхронізацію доступу. Для простих змінних може виявитися достатнім оголосити таку змінну як volatile, але в більшості ситуацій вам доведеться використовувати синхронізацію.

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

Не перестарайтеся

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

Зокрема, застосування декількох потоків не прискорює роботу прив'язаною до процесора програми в однопроцесорних системах.

Приклад: застосування одного потоку для відліку часу, а іншого - для виконання роботи

У наступному прикладі використовуються два потоки, один для відліку часу, а інший для виконання роботи. Головний потік шукає прості числа по дуже простому алгоритму

Перед початком роботи програма створює і запускає потік таймера, який протягом десяти секунд очікує, а потім встановлює прапор, що перевірявся головним потоком. Через десять секунд головний потік припинить роботу. Зверніть увагу, що цей загальний прапор оголошений як volatile.

/ **

 * CalculatePrimes - знаходить стільки простих чисел, скільки зможе знайти за десять

 * Секунд

 * /

public class CalculatePrimes extends Thread {

    public static final int MAX_PRIMES = 1000000;

    public static final int TEN_SECONDS = 10000;

    public volatile boolean finished = false;

    public void run () {

        int [] primes = new int [MAX_PRIMES];

        int count = 0;

        for (int i = 2; count <MAX_PRIMES; i ++) {

            // Перевірка, не спрацював Чи таймер

            if (finished) {

                break;

            }

            boolean prime = true;

            for (int j = 0; j <count; j ++) {

                if (i% primes [j] == 0) {

                    prime = false;

                    break;

                }

            }

            if (prime) {

                primes [count ++] = i;

                System.out.println ("Found prime:" + i);

            }

        }

    }

    public static void main (String [] args) {

        CalculatePrimes calculator = new CalculatePrimes ();

        calculator.start ();

        try {

            Thread.sleep (TEN_SECONDS);

        }

        catch (InterruptedException e) {

            // Невдача

        }

        calculator.finished = true;

    }

}

          

Підіб'ємо підсумки

Мова Java має потужні вбудовані засоби управління потоками. Ви можете використовувати ці кошти для:

Підвищення чуйності графічного інтерфейсу користувача

Реалізації переваг багатопроцесорних систем

Спрощення логіки програми при наявності декількох незалежних об'єктів

Блокування введення / виведення без блокування всієї програми

При використанні декількох потоків потрібно ретельно дотримуватися правил організації загального доступу до даних, які будуть описані в розділі Загальний доступ до даних Всі ці правила зводяться до одного простого принципу: Не забувайте про синхронізацію.

Життя потоку

Створення потоків

Існує кілька способів створення потоків в програмі Java. Кожна програма Java містить щонайменше один потік - головний потік. Додаткові потоки створюються через конструктор Thread або шляхом створення екземплярів класу, що розширюють клас Thread.

Потоки Java можуть створювати інші потоки шляхом безпосереднього створення екземплярів об'єкта Thread або об'єкта, що розширює об'єкт Thread. У прикладі, наведеному в розділі Основи потоків, в якому ми знаходили стільки простих чисел, скільки вдасться знайти за десять секунд, ми створили потік шляхом створення екземпляра об'єкта типу CalculatePrimes, який розширює об'єкт Thread.

Коли ми говоримо про потоки в програмах Java, ми можемо мати на увазі дві взаємопов'язані сутності: реальний виконує роботу потік або об'єкт Thread, який представляє цей потік. Працюючий потік, як правило, створюється операційною системою; об'єкт Thread створюється віртуальною машиною (VM) Java як засіб управління відповідним потоком.

Створення потоків і запуск потоків - це не одне і те ж

Насправді потік не починає роботу до тих пір, поки інший потік не викличе метод start () об'єкта Thread для цього нового потоку. Об'єкт Thread існує до фактичного запуску його потоку і продовжує існувати після припинення роботи потоку. Це дозволяє управляти потоком і отримувати про нього інформацію, навіть якщо потік ще не запущений або вже завершений.

Як правило, запускати потік з конструктора не рекомендується. Це може відкрити новому потоку частково створений об'єкт. Якщо об'єкт має потоком, він повинен надавати метод start () або init (), який потрібно використовувати для запуску потоку замість того, щоб запускати його з конструктора. (Посилання на статті, що дають більш докладне пояснення цієї концепції, можна знайти в розділі Ресурси).

Завершення роботи потоків

Завершити роботу потоку можна наступними трьома способами:

Потік можна завершити, викликавши його метод run ().

Потік може створити винятковий стан або помилку, які не вдасться перехопити.

Інший потік може викликати один з не рекомендованих методів stop (). Поняття «Нерекомендовані» означає, що ці методи існують, але їх не слід використовувати в новому коді і потрібно намагатися виключити з існуючого коду.

Коли всі потоки в програмі Java завершуються, програма припиняє роботу.

Об'єднання з потоками

Інтерфейс Thread API містить метод, який можна використовувати для очікування завершення іншого потоку: метод join (). Коли ви викликаєте Thread.join (), що викликає потік блокується до завершення роботи цільового потоку.

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

Планування

За винятком випадків застосування Thread.join () і Object.wait (), час запуску і виконання потоку не детерміноване. Якщо два потоки працюють одночасно і жоден з них не перебуває в режимі очікування, вам слід виходити з того, що в проміжку між двома будь-якими командами інші потоки можуть працювати і змінювати змінні програми. Якщо ваш потік буде звертатися до даних, які доступні іншим потокам, наприклад, до даних, що адресується безпосередньо або побічно з статичних полів (глобальні змінні), то для забезпечення узгодженості даних потрібно використовувати синхронізацію.

У наведеному нижче простому прикладі ми створимо і запустимо два потоки, кожен з яких надрукує два рядки на System.out::

public class TwoThreads {

    public static class Thread1 extends Thread {

        public void run () {

            System.out.println ("A");

            System.out.println ("B");

        }

    }

    public static class Thread2 extends Thread {

        public void run () {

            System.out.println ("1");

            System.out.println ("2");

        }

    }

    public static void main (String [] args) {

        new Thread1 (). start ();

        new Thread2 (). start ();

    }

}

          

Ми не маємо ні найменшого уявлення про те, в якому порядку будуть виконуватися рядки, за винятком того, що цифра "1" завжди буде друкуватися перед цифрою "2", а буква "A" перед буквою "B." У результаті може бути роздрукована одна з наступних комбінацій:

1 лютому A B

1 A 2 B

1 A B 2

A 1 лютого B

A 1 B 2

A B 2 січня

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

Режим очікування

Інтерфейс Thread API має метод sleep (), який переводить поточний потік в режим очікування, в якому той перебуває до тих пір, поки не закінчиться зазначений інтервал часу, або поки потік не буде перерваний іншим потоком, що викликав метод Thread.interrupt () для об'єкта Thread поточного потоку. Після закінчення зазначеного часу потік відновлює роботу і повертається в чергу виконуваних потоків планувальника.

Якщо потік переривається викликом методу Thread.interrupt (), сплячий потік виробляє переривання InterruptedException, в результаті чого потік буде знати, що він розбуджений перериванням, і не буде проводити перевірку спрацьовування таймера.

Метод Thread.yield () подібний до методу Thread.sleep (), але замість усипляння поточного потоку він миттєво ставить його на паузу, дозволяючи працювати іншим потокам. У більшості реалізацій потоки з меншим пріоритетом припиняють роботу, якщо потік з вищим пріоритетом викликає метод Thread.yield ().

У прикладі CalculatePrimes фоновий потік використовувався для пошуку простих чисел, після чого засинав на десять секунд. Після закінчення заданого інтервалу часу таймер встановлював прапор, який показує, що десять секунд минули.

Потоки-демони

Ми вже говорили про те, що програма Java завершує роботу, коли завершили роботу всі її потоки, але це не зовсім так. А як же приховані системні потоки, такі як потік збору сміття та інші службові потоки, створені JVM? Ми не можемо їх зупинити. Якщо ці потоки продовжують працювати, як же програма Java взагалі завершить роботу?

Ці системні потоки називаються демонами. Насправді програма Java завершує роботу, якщо завершені всі її потоки, які не є демонами.

Демоном може стати будь-який потік. Ви можете призначити потік демоном, викликавши метод Thread.setDaemon ().Потоки-демони можна використовувати в якості фонових потоків вашої програми, таких як потоки таймерів або потоки інших відстрочених подій, які корисні лише в тому випадку, коли працюють інші потоки, які не є демонами.

Приклад: розбиття великої задачі на частини за допомогою декількох потоків

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

/ **

 * Створює десять потоків для пошуку максимального значення в великій матриці.

 * Кожен потік обстежує один з фрагментів матриці.

 * /

public class TenThreads {

    private static class WorkerThread extends Thread {

        int max = Integer.MIN_VALUE;

        int [] ourArray;

        public WorkerThread (int [] ourArray) {

            this.ourArray = ourArray;

        }

        // Пошук максимального значення в деякому фрагменті масиву

        public void run () {

            for (int i = 0; i <ourArray.length; i ++)

                max = Math.max (max, ourArray [i]);

        }

        public int getMax () {

            return max;

        }

    }

    public static void main (String [] args) {

        WorkerThread [] threads = new WorkerThread [10];

        int [] [] bigMatrix = getBigHairyMatrix ();

        int max = Integer.MIN_VALUE;

        

        // Виділення кожному потоку фрагмента матриці, з яким він буде працювати

        for (int i = 0; i <10; i ++) {

            threads [i] = new WorkerThread (bigMatrix [i]);

            threads [i] .start ();

        }

        // Очікування завершення роботи потоку

        try {

            for (int i = 0; i <10; i ++) {

                threads [i] .join ();

                max = Math.max (max, threads [i] .getMax ());

            }

        }

        catch (InterruptedException e) {

            // Невдача

        }

        System.out.println ("Maximum value was" + max);

    }

}

          

 

Підіб'ємо підсумки

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

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

Метод sleep () можна використовувати для перекладу потоку в режим очікування на деякий час; метод join () можна використовувати для очікування завершення іншого потоку.

Потоки всюди

Хто створює потоки?

Навіть якщо ви ніколи не створювали новий потік в явному вигляді, ви цілком можете виявити, що все-таки працюєте з ними. Потоки можуть потрапляти у ваші програми з самих різних джерел.

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

AWT і Swing

Будь використовує AWT або Swing програмою доводиться мати справу з потоками. Набір інструментів AWT створює один потік для обробки подій інтерфейсу користувача (UI), а будь-які приймачі подій, викликані подіями AWT, виконують в AWT потік, що обробляє події.

Ви повинні не тільки подбати про те, щоб синхронізувати доступ до даних, спільно використовуваних приймачами подій та іншими потоками, але і знайти спосіб виконання тривалих завдань, що запускаються приймачами подій, таких як перевірка орфографії у великому документі або пошук файлу у файловій системі, яка не приводив би до зависання користувацького інтерфейсу під час виконання завдання (що також утримує користувача від скасування операції). Хорошим прикладом системи, що вирішує цю проблему, є клас SwingWorker (див. Розділ Ресурси).

Потоки обробки подій AWT не є демонами, тому для завершення додатків AWT і Swing часто використовується метод System.exit ().

Використання TimerTask

Інструмент TimerTask вперше був представлений в мові Java в JDK 1.3. Цей зручний інструмент дозволяє відкласти виконання завдання (наприклад, одноразово виконати завдання через десять секунд) або виконувати завдання періодично (наприклад, через кожні десять секунд).

Реалізація класу Timer досить проста: вона створює потік таймера і будує чергу очікують подій, відсортованих за часом виконання.

Потік TimerTask позначений як демон і тому не перешкоджає завершенню програми.

Оскільки події таймера виконуються в потоці таймера, ви повинні забезпечити відповідну синхронізацію доступу до будь-яких об'єктів даних, використовуваним з завдання таймера.

У прикладі CalculatePrimes замість того, щоб переводити головний потік в сплячий стан, ми могли б застосувати TimerTask наступним чином:

    public static void main (String [] args) {

        Timer timer = new Timer ();

        

        final CalculatePrimes calculator = new CalculatePrimes ();

        calculator.start ();

        timer.schedule (

                new TimerTask () {

                    public void run ()

                    {

                        calculator.finished = true;

                    }

                }, TEN_SECONDS);

    }

          

Сервлети і технологія сторінок JavaServer

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

При написанні сервлетів або файлів сторінок JavaServer (JSP) ви завжди повинні мати на увазі, що один і той же сервлет або JSP може одночасно виконуватися в декількох потоках. Будь загальні дані, до яких звертається сервлет або файл JSP, повинні бути відповідним чином синхронізовані; це відноситься і до полів самого об'єкта сервлету.

Реалізація об'єкта RMI

Функція RMI дозволяє виконувати операції з об'єктами, що працюють на інших JVM. Коли ви викликаєте віддалений метод, створена компілятором RMI заглушка RMI упаковує параметри методу і передає їх по мережі в віддалену систему, яка розпаковує їх і викликає віддалений метод.

Припустимо, ви створюєте об'єкт RMI і реєструєте його в реєстрі RMI або в просторі імен API мови Java для доступу до сервісів імен і каталогів (JNDI). У якому потоці виконуватиметься метод при його виклику віддаленим клієнтом?

Поширений спосіб реалізації об'єкта RMI полягає в розширенні класу UnicastRemoteObject. У процесі конструювання UnicastRemoteObject инициализируется інфраструктура диспетчеризації викликів віддалених методів. Вона включає приймач сокетов, який приймає запити віддалених викликів, і один або декілька потоків для виконання віддалених запитів.

Тому коли ви приймаєте запит на виконання методу RMI, ці методи виконуються в потоці, керованому RMI.

Підіб'ємо підсумки

Потоки виникають в програмах Java декількома способами. Крім явного створення потоків за допомогою конструкторів Thread, потоки створюються безліччю інших механізмів:

AWT і Swing

RMI

Інструментом java.util.TimerTask

Сервлетами і технологією JSP

Загальний доступ до даних

Загальний доступ до змінних

Щоб отримати користь від застосування в програмі декількох потоків, вони повинні вміти взаємодіяти між собою або обмінюватися один з одним результатами своєї роботи.

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

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

Всі потоки живуть в одній області пам'яті

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

Будь-яка доступна змінна доступна всім потокам, в тому числі і головному потоку. У прикладі з пошуком простих чисел для індикації закінчення встановленого часу використовувалося публічне поле примірника з ім'ям finished. Один потік виконував запис в це поле, коли таймер закінчував відлік, а інший періодично зчитував це поле і перевіряв, чи не пора зупинитися. Зверніть увагу, що це поле було оголошено як volatile, що дуже важливо для правильної роботи програми. Чому це так, ми дізнаємося в цьому розділі пізніше.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]