Рина Сергеева
Рина Сергеева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();
        }
    }
}

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

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

Мы видим, что потоки выполняют программный код случайным образом. Из-за этого «покупатели» складывают товар в корзины, но остаются без товара.

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

Work of the program using the synchronization unit

Теперь программа работает именно так, как мы и ожидали. Большой!

Интерфейсный канал

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

Канал представляет собой открытое соединение с объектом, таким как аппаратное устройство, файл, сетевой сокет или программный компонент, который способен выполнять одну или несколько различных операций ввода-вывода, например чтение или запись.
Канал либо открыт, либо закрыт. Канал открыт при создании и после закрытия остается закрытым. Как только канал закрыт, любая попытка вызвать для него операцию ввода-вывода вызовет исключение ClosedChannelException . Открыт ли канал, можно проверить, вызвав его методы isOpen() и tryLock(). ).

Java package.nio.channels содержит такие каналы, как:

  • AsynchronousChannel
  • ByteChannel
  • NetworkChannel
  • FileChannel
  • InterruptibleChannel
  • MulticastChannel and other.

Пример

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

Создадим два проекта, отличающихся только тем, что они будут записаны в файл — «Программа 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 хостинг.

Вам это нравится? Поделитесь в социальных сетях!

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
d
  • dsfs
  • 26 апреля 2024 г. 11:56

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

  • Результат:80баллов,
  • Очки рейтинга4
d
  • dsfs
  • 26 апреля 2024 г. 11:45

C++ - Тест 002. Константы

  • Результат:50баллов,
  • Очки рейтинга-4
d
  • dsfs
  • 26 апреля 2024 г. 11:35

C++ - Тест 001. Первая программа и типы данных

  • Результат:73баллов,
  • Очки рейтинга1
Последние комментарии
k
kmssr9 февраля 2024 г. 2:43
Qt Linux - Урок 001. Автозапуск Qt приложения под Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
АК
Анатолий Кононенко5 февраля 2024 г. 9:50
Qt WinAPI - Урок 007. Работаем с ICMP Ping в Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25 декабря 2023 г. 18:30
Boost - статическая линковка в CMake проекте под Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
J
JonnyJo25 декабря 2023 г. 16:38
Boost - статическая линковка в CMake проекте под Windows Сделал всё по-как у вас, но выдаёт ошибку [build] LINK : fatal error LNK1104: не удается открыть файл "libboost_locale-vc142-mt-gd-x64-1_74.lib" Хоть убей, не могу понять в чём дел…
G
Gvozdik19 декабря 2023 г. 5:01
Qt/C++ - Урок 056. Подключение библиотеки Boost в Qt для компиляторов MinGW и MSVC Для решения твой проблемы добавь в файл .pro строчку "LIBS += -lws2_32" она решит проблему , лично мне помогло.
Сейчас обсуждают на форуме
IscanderChe
IscanderChe30 апреля 2024 г. 11:22
Во Flask рендер шаблона не передаётся в браузер Доброе утро! Имеется вот такой шаблон: <!doctype html><html> <head> <title>{{ title }}</title> <link rel="stylesheet" href="{{ url_…
G
Gar22 апреля 2024 г. 12:46
Clipboard Как скопировать окно целиком в clipb?
DA
Dr Gangil Academics20 апреля 2024 г. 14:45
Unlock Your Aesthetic Potential: Explore MSC in Facial Aesthetics and Cosmetology in India Embark on a transformative journey with an msc in facial aesthetics and cosmetology in india . Delve into the intricate world of beauty and rejuvenation, guided by expert faculty and …
a
a_vlasov14 апреля 2024 г. 13:41
Мобильное приложение на C++Qt и бэкенд к нему на Django Rest Framework Евгений, добрый день! Такой вопрос. Верно ли следующее утверждение: Любое Android-приложение, написанное на Java/Kotlin чисто теоретически (пусть и с большими трудностями) можно написать и на C+…
Павел Дорофеев
Павел Дорофеев14 апреля 2024 г. 9:35
QTableWidget с 2 заголовками Вот тут есть кастомный QTableView с многорядностью проект поддерживается, обращайтесь

Следите за нами в социальных сетях