Рина Сергеева
Рина Сергеева28 листопада 2018 р. 13:58

Кілька способів синхронізації процесів і потоків в Java

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

Критична секція

Цей метод підходить вам, якщо:

  • паралельні потоки працюють із загальним ресурсом;
  • Потрібна синхронізація між потоками, а не процесами;

Цей метод синхронізації називається синхронізацією ресурсів (синхронізація «відкрити - закрити»). Ідея цього методу полягає в тому, що кожен об'єкт у Java має пов'язаний з ним монітор. Монітор - це свого роду інструмент управління доступом до об'єкта.


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

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

Приклад

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

public class Program {

    private static final Boolean key = true;                      //the object will be used for synchronization
                                                                  //it must be final 
    private static Integer amountOfGoods = 20;


    public static class Buyer implements Runnable {

        @Override
        public void run() {
            final int goodsInTheShoppingCart = (int) (1 + Math.random() * amountOfGoods);   //the number of goods that the buyer will put in the basket
            synchronized (key) {
                if (amountOfGoods - goodsInTheShoppingCart >= 0) {
                    System.out.println("The " + Thread.currentThread().getName() + " placed " + goodsInTheShoppingCart + " items in the basket.");
                    if ((amountOfGoods = amountOfGoods - goodsInTheShoppingCart) < 0) {
                        System.out.println("Whoops! The product is out of stock, but the " + Thread.currentThread().getName() + " didn't know about it.");
                        return;
                    } else {
                        System.out.println("There are " + amountOfGoods + " items left in the store.");
                    }
                } else {
                    System.out.println("The " + Thread.currentThread().getName() + " is notified that the goods are over.");
                    return;
                }
            }
            System.out.println("The " + Thread.currentThread().getName() + " made a purchase.");
        }
    }

    public static void main(String[] args) {

        for (int i = 1; i <= 5; i++) {
            Thread t = new Thread(new Buyer());              // create five threads
            t.setName("buyer " + i);
            System.out.println("The " + t.getName() + " went to the store website.");
            t.start();
        }
    }
}

Робота програми до використання блоку синхронізації

Робота програми до використання блоку синхронізації

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

Тепер додамо блок синхронізації та подивимося, як зміниться робота програми.

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

Тепер програма працює саме так, як ми очікували. Великий!

Інтерфейсний канал

Ви можете використовувати інтерфейсний канал, якщо вам потрібно синхронізувати різні процеси. Канал створюється на рівні JVM і буде єдиним у системі.

>Канал являє собою відкрите з'єднання з об'єктом, таким як апаратний пристрій, файл, мережний сокет або програмний компонент, який здатний виконувати одну або кілька різних операцій введення-виводу, наприклад, читання або запис.
> Канал або відкритий або закритий. Канал відкритий при створенні та після закриття залишається закритим. Як тільки канал закритий, будь-яка спроба викликати для нього операцію вводу-виводу викликає виняток ClosedChannelException . Чи відкритий канал можна перевірити, викликавши його методи isOpen() і tryLock(). ).

Java package.nio.channels містить такі канали, як:

  • Асинхронний канал
  • ByteChannel
  • Мережевий канал
  • Файловий канал
  • InterruptibleChannel
  • MulticastChannel та інші.

Приклад

Два процеси записують дані у файл. Вони мають записувати по черзі, не заважаючи роботі один одного.

Створимо два проекти, які відрізняються лише тим, що вони будуть записані у файл - "Програма 1" або "Програма 2". Ми запускаємо їх одночасно.

public class Program {


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

        File file = new File("C:\\text.txt");
        FileChannel fileChannel = new FileOutputStream(file, true).getChannel();

        while(fileChannel.tryLock() == null){            // the process checks if the channel is free to write to the file
            Thread.sleep(10000);
        }
        try {
            String lineSeparator = System.getProperty("line.separator");

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

                String newData = "The program 2 is recording." + lineSeparator;
                ByteBuffer buf = ByteBuffer.allocate(200);
                buf.put(newData.getBytes());
                buf.flip();
                fileChannel.write(buf);
                Thread.sleep(1000);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
        fileChannel.close();
    }
}

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

Тепер подивимося, що буде у файлі, якщо ми використовуватимемо канали.

Це саме те, що нам потрібне! Процеси одержують доступ до файлу один за одним.

Події

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

Це «очікування-повідомлення» синхронізації:

  • Паралельні потоки чекають деякої події;
  • один з потоків відповідає за виникнення події і повідомляє інші потоки (один за допомогою notify або всі - notifyAll,) розблоковуючи їх:
  • підходить тільки для синхронізації потоків, а чи не процесів;

Приклад

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

public class Program {

    public static void main(String[] args) {

        WeaponStore weaponStore = new WeaponStore();      
        Arsenal arsenal = new Arsenal(weaponStore);
        Shooter shooter = new Shooter(weaponStore);
        new Thread(arsenal).start();
        new Thread(shooter).start();
    }
}


class WeaponStore {                                   

    private int cartridges = 0;                       

    public synchronized void shot() {                 // synchronization of the method is required
                                                      // because two threads will have access to it
        while (cartridges < 1) {
            try {
                wait();
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
                                                      //was thrown out notify by another thread, then the cartridge is received
        cartridges--;
        System.out.println("Produced 1 shot." + " Cartridges in the store: " + cartridges);
        notify();
    }

    public synchronized void reload(int arsenalCartriges) {   // synchronization of the method is required
                                                              // because two threads will have access to it
        while (cartridges >= 6) {
            try {
                wait();
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        cartridges++;
        System.out.println("1 cartridge has been added to the store." + " Cartridges in stock: " + arsenalCartriges);
        notify();
    }
}

class Arsenal implements Runnable{                     

    private int cartridges = 15;

    private WeaponStore weapon;

    Arsenal(WeaponStore weapon){
        this.weapon = weapon;
    }

    public void run(){
        for (int cartridges = 15; cartridges > 0; cartridges--) {
            weapon.reload(cartridges);
        }
    }
}


class Shooter implements Runnable{                     

    private  WeaponStore weapon;

    Shooter(WeaponStore weapon){
        this.weapon = weapon;
    }

    public void run(){
        for (int i = 1; i <= 15; i++) {
            weapon.shot();
        }
    }
}

Результат програми

1 cartridge has been added to the store. Cartridges in stock: 15 
1 cartridge has been added to the store. Cartridges in stock: 14 
1 cartridge has been added to the store. Cartridges in stock: 13 
1 cartridge has been added to the store. Cartridges in stock: 12 
1 cartridge has been added to the store. Cartridges in stock: 11 
Produced 1 shot. Cartridges in the store: 4 
Produced 1 shot. Cartridges in the store: 3 
Produced 1 shot. Cartridges in the store: 2 
Produced 1 shot. Cartridges in the store: 1 
Produced 1 shot. Cartridges in the store: 0 
1 cartridge has been added to the store. Cartridges in stock: 10 
1 cartridge has been added to the store. Cartridges in stock: 9 
1 cartridge has been added to the store. Cartridges in stock: 8 
1 cartridge has been added to the store. Cartridges in stock: 7 
1 cartridge has been added to the store. Cartridges in stock: 6 
1 cartridge has been added to the store. Cartridges in stock: 5 
Produced 1 shot. Cartridges in the store: 5 
Produced 1 shot. Cartridges in the store: 4 
Produced 1 shot. Cartridges in the store: 3 
Produced 1 shot. Cartridges in the store: 2 
Produced 1 shot. Cartridges in the store: 1 
Produced 1 shot. Cartridges in the store: 0 
1 cartridge has been added to the store. Cartridges in stock: 4 
1 cartridge has been added to the store. Cartridges in stock: 3 
1 cartridge has been added to the store. Cartridges in stock: 2 
1 cartridge has been added to the store. Cartridges in stock: 1 
Produced 1 shot. Cartridges in the store: 3 
Produced 1 shot. Cartridges in the store: 2 
Produced 1 shot. Cartridges in the store: 1 
Produced 1 shot. Cartridges in the store: 0

Висновок

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

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

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Стабільний хостинг, на якому розміщується соціальна мережа EVILEG. Для проектів на Django радимо VDS хостинг.

Вам це подобається? Поділіться в соціальних мережах!

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50бали,
  • Рейтинг балів-4
m
  • molni99
  • 26 жовтня 2024 р. 11:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
m
  • molni99
  • 26 жовтня 2024 р. 11:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20бали,
  • Рейтинг балів-10
Останні коментарі
ИМ
Игорь Максимов22 листопада 2024 р. 22:51
Django - Підручник 017. Налаштуйте сторінку входу до Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi01 листопада 2024 р. 00:37
Django - Урок 064. Як написати розширення для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 жовтня 2024 р. 18:19
Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов05 жовтня 2024 р. 17:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 21:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Evgenii Legotckoi
Evgenii Legotckoi25 червня 2024 р. 01:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 листопада 2024 р. 17:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject04 червня 2022 р. 13:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 жовтня 2024 р. 19:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Слідкуйте за нами в соціальних мережах