JavaRush /Java блог /Random /Кофе-брейк #237. Пользовательская синхронизация в Java с ...

Кофе-брейк #237. Пользовательская синхронизация в Java с использованием встроенных блокировок, wait() и notify(). JDK 21: знакомимся с новыми функциями в Java 21

Статья из группы Random

Пользовательская синхронизация в Java с использованием встроенных блокировок, wait() и notify()

Источник: Medium Перед вами подробный пример реализации BlockingQueue (блокирующей очереди) с помощью блоков wait(), notify() и synchronized. Кофе-брейк #237. Пользовательская синхронизация в Java с использованием встроенных блокировок, wait() и notify(). JDK 21: знакомимся с новыми функциями в Java 21 - 1Иногда на собеседованиях вас могут попросить привести пример использования wait() и notify(). Давайте разберем, как и где их нужно применять.

wait() и notify(): что они делают и как их использовать

Как правило, wait() блокирует выполнение (очевидно, текущего потока) до тех пор, пока на том же мониторе не будет вызван соответствующий метод notify(). На практике это происходит внутри метода synchronized (где monitor — это класс, поэтому у вас может быть только один монитор) или блока synchronized, где вы сами решаете, какой монитор использовать.

wait() и notify() с использованием методов synchronized

Вот пример, когда я вызываю метод synchronized, который находится в режиме ожидания до тех пор, пока я не вызову notify(), чтобы выполнение могло продолжаться.

public class WaitNotifyExampleSyncMethods {
    synchronized void unlockAndContinue() {
        notify();
    }

    synchronized void lockUntilNotified() throws InterruptedException {
        System.out.println("Waiting...");
        wait();
        System.out.println("Notified. I can now continue");
    }

    public static void main(String[] args) throws InterruptedException {
        WaitNotifyExampleSyncMethods waitNotifyExample = new WaitNotifyExampleSyncMethods();
        new Thread(() -> {
            try {
                Thread.sleep(500);
                waitNotifyExample.unlockAndContinue();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
        waitNotifyExample.lockUntilNotified();
        System.out.println("End of main method");
    }

}
Вывод:
Waiting... Notified. I can now continue End of main method

wait() и notify() с использованием блоков synchronized

В случае, если я хочу использовать настраиваемый объект в качестве монитора (обычно это предпочтительнее, если вам нужно более одного в том же классе), то вот эквивалентный код, дающий один и тот же вывод. Обратите внимание, что monitor представляет собой простой объект Java, и я вызываю wait() и notify() для этого объекта из блоков synchronized, также используя тот же монитор/объект.

public class WaitNotifyExampleCustomMonitor {
    public static void main(String[] args) throws InterruptedException {
        Object monitor = new Object();
        new Thread(() -> {
            try {
                Thread.sleep(500);
                synchronized (monitor) {
                    monitor.notify(); // <-- (1)
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }).start();
        synchronized (monitor) {
            System.out.println("Waiting...");
            monitor.wait(); // <-- (2)
            System.out.println("Notified. I can now continue");
        }
        System.out.println("End of main method");
    }
}

BlockingQueue (если вы не знали)

BlockingQueue (блокирующая очередь) — это очередь Queue, которая предназначена для реализации взаимодействия producer/consumer (поставщик/потребитель). BlockingQueue имеет дополнения:
  • Блокирующая операция take() — берет следующий элемент, если же очередь пустая, то блокирует выполнение до появления элемента (до момента, когда очередь перестанет быть пустой).
  • Блокирующая операция put() — помещает элемент в очередь, если же очередь заполнена, то блокирует выполнение до освобождения места в очереди и успешного помещения в нее нового элемента.

Реализация блокирующей очереди BlockingQueue с использованием wait() и notify()

Ниже я вставлю небольшой пример кода, который содержит простые элементы LinkedList и два потока, которые я запускаю и жду их завершения в методе main:
  • Поток-производитель (producer thread) помещает в список 1000 элементов. Обратите внимание, что если очередь заполнена, то я вызываю wait() до тех пор, пока она не освободится. После того, как элемент добавлен, я вызываю его notify(), чтобы поток-потребитель (consumer thread), ожидающий освобождения элементов, знал, что уже есть что-то для потребления.
  • Поток-потребитель работает в режиме демона (daemon mode) под while(true) и останавливается, если получает poison pill (это просто последний элемент). Как и в первом случае, если очередь пуста, то wait() вызывается до тех пор, пока ситуация не изменится. После того, как элемент использован, я вызываю его notify(), чтобы поток-производитель (возможно, ожидающий заполнения очереди) знал, что теперь есть свободное место для добавления хотя бы одного элемента.

import java.util.LinkedList;
import java.util.Random;

public class ProducerConsumerCustomSync {
    LinkedList<Integer> queue = new LinkedList<>();
    static final int LIMIT = 10;
    Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        ProducerConsumerCustomSync pc = new ProducerConsumerCustomSync();
        Thread producerThread = new Thread(() -> {
            try {
                pc.produce();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });

        Thread consumerThread = new Thread(() -> {
            try {
                pc.consume();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });

        producerThread.start();
        consumerThread.start();
        producerThread.join();
        consumerThread.join();
        System.out.println("end");
    }

    public void produce() throws InterruptedException {
        int i = 0;
        while (i < 1000) {
            synchronized (lock) {
                while (queue.size() == LIMIT) {
                    System.out.println("queue is full. wait for consumer to pop...");
                    lock.wait();
                }
                queue.add(i++);
                if (i%100 == 0) {
                    Thread.sleep(new Random().nextInt(200));
                }
                lock.notify(); // разблокировать consumer, если он ожидает элементы
            }
        }
    }

    public void consume() throws InterruptedException {
        while (true) {
            synchronized (lock) {
                while (queue.size() == 0) {
                    System.out.println("queue is empty. wait for producer to add items...");
                    lock.wait();
                }
                Integer pop = queue.removeFirst();
                lock.notify(); // разблокировать producer, если он быть заполнен
                System.out.printf("Taken %d from queue\n", pop);
                if (pop == 999) {
                    System.out.printf("poison pill received !");
                    return;
                }
            }
        }
    }

}
Запуск вышеуказанного кода приводит к следующему:
queue is full. wait for consumer to pop... Taken 0 from queue ... Taken 9 from queue queue is empty. wait for producer to add items... queue is full. wait for consumer to pop... Taken 10 from queue ... Taken 18 from queue Taken 19 from queue queue is empty. wait for producer to add items... queue is full. wait for consumer to pop... Taken 20 from queue ... Taken 985 from queue Taken 986 from queue queue is empty. wait for producer to add items... queue is full. wait for consumer to pop... Taken 987 from queue Taken 988 from queue ... Taken 996 from queue queue is empty. wait for producer to add items... Taken 997 from queue Taken 998 from queue Taken 999 from queue poison pill received !end Process finished with exit code 0

JDK 21: знакомимся с новыми функциями в Java 21

Источник: Medium Готовы ли вы к следующему большому обновлению в мире Java? Приготовьтесь изучить удивительные функции, которые уже совсем скоро нам предложит JDK 21! JDK 21 — новая версия Java с долгосрочной поддержкой (LTS), релиз которой назначен на 19 сентября этого года. Релиз получит пять лет основной поддержки и расширенную поддержку до сентября 2031 года. Давайте рассмотрим несколько функций, которые сделают JDK 21 революционной.

1. Виртуальные потоки: упрощение и масштабирование параллелизма

Одной из самых ожидаемых функций Java 21 являются виртуальные потоки (Virtual Threads), которые должны обеспечить простоту и масштабируемость параллельного программирования. Позволяя серверным приложениям масштабироваться с оптимальным использованием оборудования, виртуальные потоки упрощают написание и обслуживание параллельных приложений с высокой пропускной способностью. С минимальными изменениями, необходимыми для существующего кода, использующего API lang.Thread, виртуальные потоки открывают целый мир возможностей для разработчиков.

2. Generational ZGC: сборка мусора с меньшими накладными расходами

В Java 21 появится генерационный ZGC (Generational ZGC) — сборщик мусора, предназначенный для сокращения накладных расходов на сборку мусора. Поддерживая отдельные поколения для молодых и старых объектов, ZGC может чаще собирать молодые объекты, что приводит к повышению производительности приложений. Благодаря более низким рискам зависания, уменьшенным расходам памяти кучи и процессора на сборку мусора, приложения, работающие с Generational ZGC, получат значительные преимущества.

3. КЕМ API (Key Encapsulation Mechanism, Механизм инкапсуляции ключей)

Усиленная безопасность шифрования является главным приоритетом в Java 21, и добавление KEM API (Key Encapsulation Mechanism, KEM) отлично это отражает. KEM API позволяет приложениям использовать механизмы инкапсуляции ключей для защиты симметричных ключей с помощью общедоступной криптографии. Благодаря поддержке таких алгоритмов, как RSA-KEM и ECIES, разработчики могут улучшить шифрование в своих приложениях и интегрировать KEM в протоколы более высокого уровня, такие как TLS.

4. Структурированный параллелизм: упрощение параллельного программирования

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

5. Ограниченные значения (Scoped Values): безопасное совместное использование неизменяемых данных

Локальные переменные потока имеют ограничения, особенно при работе с большим количеством виртуальных потоков. Ограниченные значения (Scoped Values) обеспечивают более безопасную альтернативу для совместного использования неизменяемых данных внутри и между потоками. Позволяя совместно использовать данные, не прибегая к аргументам метода, Scoped Values улучшают производительность в крупных приложениях.

6. Прекращение поддержки 32-разрядного порта x86 для Windows: движение к будущему

В перспективе 32-разрядный порт Windows x86 помечен для удаления в будущем выпуске Java. Это устаревание совпадает с прекращением поддержки 32-разрядных версий Windows 10 в октябре 2025 года. Разработчикам рекомендуется обновить свои системы сборки и рассмотреть альтернативные архитектуры для обеспечения совместимости с будущими выпусками Java. Это лишь некоторые из интересных функций, которые предлагает JDK 21. Наличие в Java 21 самых различных обновлений: от улучшенного шифрования до упрощенного параллелизма и новой сборки мусора, расширяет возможности разработчиков и революционизирует программирование на Java.
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ