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

Потоки, як і рядки, представлені класом в стандартних бібліотеках Java Щоб породити новий потік виконання, для початку слід створити обєкт Thread:

Thread worker = new Thread()

Після того як обєкт-потік буде створений, ви можете задати його конфігурацію і запустити У поняття конфігурації потоку входить вказівка ​​вихідного пріоритету, імені і так далі Коли потік готовий до роботи, слід викликати його метод start Метод start породжує новий виконуваний потік на основі даних обєкта класу Thread, після

чого завершується Метод start викликає метод run нового потоку, що призводить до

активізації останнього

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

Стандартна реалізація Threadrun не робить нічого Ви повинні або розширити клас Thread, щоб включити в нього новий метод run, або створити обєкт Runnable і передати його конструктору потоку Спочатку ми розглянемо процес породження нових потоків за рахунок розширення Thread, а пізніше займемося технікою роботи з Runnable (див Використання Runnable)

Наведена нижче проста програма задіює два потоки, які виводять слова

“Ping і PONG з різною частотою:

class PingPong extends Thread {

String word / / Виведене слово

int delay / / Тривалість паузи

PingPong(String whatToSay, int delayTime) {

word = whatToSay

delay = delayTime

}

public void run() {

try {

for (;) {

Systemoutprint(word + &quot &quot)

sleep (delay) / / Почекати наступного виведення

}

} catch (InterruptedException e) {

return

}

}

public static void main(String[] args) {

new PingPong (ping, 33) start () / / 1/30 секунди

new PingPong (PONG, 100) start () / / 1/10 секунди

}

}

Ми визначили тип потоку з імям PingPong Його метод run працює в нескінченному циклі, виводячи вміст поля word і роблячи паузу на delay мікросекунд Метод PingPongrun не може порушувати винятків, оскільки цього не робить переобумовленої ним метод Threadrun Відповідно, ми повинні перехопити виняток InterruptedException, яке може порушуватися методом sleep

Після цього можна безпосередньо створити виконуються потоки – саме це і робить метод PingPong Він конструює два обєкти PingPong, кожен з яких має своїм виведеним словом і інтервалом затримки, після чого викликає методи start обох обєктів-потоків З цього моменту і починається робота потоків Приблизний результат роботи може виглядати наступним чином:

ping PONG ping ping PONG ping ping ping PONG ping ping PONG ping ping ping PONG ping ping PONG ping ping ping PONG ping ping ping PONG ping ping PONG ping ping ping PONG ping ping ping PONG ping ping PONG ping ping ping PONG ping ping ping PONG ping

ping ping PONG ping ping PONG ping ping ping PONG ..

Потік може володіти імям, яке передається у вигляді параметра типу String або конструктору, або методом setName Ви отримаєте поточне імя потоку, якщо викличете метод getName Імена потоків передбачені виключно для зручності програміста – в системі runtime в Java вони не використовуються

Виклик статичного методу ThreadcurrentThread дозволяє отримати обєкт Thread,

який відповідає працюючому зараз потоку

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

Згадаймо приклад зі службовцями банку, про які ми говорили на початку глави Коли два працівника (потоку) повинні скористатися однією і тією ж папкою (обєктом),

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

921 Методи synchronized

Щоб клас міг використовуватися в багатопотокової середовищі, необхідно оголосити відповідні методи з атрибутом synchronized (пізніше ми дізнаємося, що ж входить у поняття відповідні) Якщо деякий потік викликає метод synchronized, то відбувається блокування обєкта Виклик методу synchronized того ж обєкта іншим потоком буде припинений до зняття блокування

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

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

Наведемо приклад того, як міг би виглядати клас Account, спроектований для роботи в багатопотокової середовищі:

class Account {

private double balance

public Account(double initialDeposit) {

balance = initialDeposit

}

public synchronized double getBalance() {

return balance

}

public synchronized void deposit(double amount) {

balance += amount

}

}

А тепер ми пояснимо, що ж означає поняття відповідні стосовно до синхронізованим методам

Конструктор не зобовязаний бути synchronized, оскільки він виконується тільки при створенні обєкта, а це може відбуватися тільки в одному потоці для кожного знову створюваного обєкта Поле balance захищено від будь-яких несинхронних змін за рахунок використання методів доступу, оголошених synchronized У цьому полягає ще одна причина, по якій замість оголошення полів public або protected слід застосовувати методи для роботи з ними: так ви зможете контролювати синхронізацію доступу до

ним

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

результаті могло б вийти частково спотворене значення Оголошення synchronized

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

Методи класу також можуть синхронізуватися з використанням блокування на рівні класу Два потоки не можуть одночасно виконувати синхронізовані статичні методи одного класу Блокування статичного методу на рівні класу не відбивається на обєктах останнього – ви можете викликати синхронізований метод для обєкта, поки інший потік заблокував весь клас в синхронізованому статичному методі В останньому випадку блокуються тільки синхронізовані статичні методи

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

922 Оператори synchronized

Оператор synchronized дозволяє виконати синхронізований фрагмент програми, який здійснює блокування обєкта, не вимагаючи від програміста виклику синхронізованого методу для даного обєкта Оператор synchronized складається з двох частин: вказівки блокируемого обєкта та оператора, виконуваного після отримання блокування Загальна форма оператора synchronized виглядає наступним чином:

synchronized (вираз)

оператор

Взяте в дужки вираз має вказувати на блокується обєкт – зазвичай воно є посиланням на обєкт Після блокування виконується оператор – Так, немов для даного обєкта виконується синхронізований метод Найчастіше при блокуванні обєкта необхідно виконати відразу декілька операторів, так що оператор, Як правило, являє собою блок Наведений нижче метод замінює кожен елемент числового масиву його модулем, причому доступ до масиву регулюється оператором synchronized:

/ ** Зробити всі елементи масиву невідємними * /

public static void abs(int[] values) {

synchronized (values) {

for (int i = 0 i &lt&lt valueslength i++) {

if (values[i] &lt&lt 0)

values[i] = – values[i]

}

}

}

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

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

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

Іноді розробник класу не бере до уваги його можливе використання в багатопотокової середовищі і не синхронізує ніякі методи Щоб застосувати такий клас в багатопотокової середовищі, у вас є дві можливості:

Створити розширений клас, в якому ви переобумовленої потрібні методи, розкажете їх synchronized і перенаправляєте виклики цих методів за допомогою посилання super

Скористатися оператором synchronized для забезпечення доступу до обєкта, з яким можуть працювати декілька потоків

У загальному випадку розширення класу є більш вдалим рішенням – воно усуває наслідки можливої ​​помилки програміста, що забуває внести доступ до обєкта в оператор synchronized Проте, якщо синхронізація необхідна лише в одному-двох фрагментах програми, то оператор synchronized надає більш просте рішення

Джерело: Арнольд К, Гослінг Д – Мова програмування Java (1997)

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*