Как получить гарантированный дедлок?
Сначала поговорим о том, что это такое. Deadlock – это взаимная блокировка, ситуация, когда два или более потока «наступают друг-другу на хвост» – зависают в вечном ожидании ресурсов, захваченных друг другом.
Livelock – похожая проблема, с тем лишь отличием, что потоки не останавливаются, а вместо этого зацикливаются, выполняя одни и те же бесполезные действия, ходят по кругу.
Стандартный подход к обеспечению гарантии защиты от дедлока – установка строгого порядка взятия блокировок. Если для мониторов A и B соблюдается всеобщий порядок захвата AB (и соответственно отпускания BA), то ни с одним потоком не случится попасть на ожидание B, успешно при этом захватив A.
Из этого можно догадаться, простой способ гарантировать возможность дедлока – явно нарушить это условие.
Нарушение условия даст дедлок «скорее всего когда-нибудь». Чтобы получить его точно и с первого раза, нужно гарантировать, что оба потока окажутся на этапе между захватами одного и другого ресурса в одно время. Это можно сделать множеством способов, в примере ниже использован CyclicBarrier.
Java Guru🤓 #java
Сначала поговорим о том, что это такое. Deadlock – это взаимная блокировка, ситуация, когда два или более потока «наступают друг-другу на хвост» – зависают в вечном ожидании ресурсов, захваченных друг другом.
Livelock – похожая проблема, с тем лишь отличием, что потоки не останавливаются, а вместо этого зацикливаются, выполняя одни и те же бесполезные действия, ходят по кругу.
Стандартный подход к обеспечению гарантии защиты от дедлока – установка строгого порядка взятия блокировок. Если для мониторов A и B соблюдается всеобщий порядок захвата AB (и соответственно отпускания BA), то ни с одним потоком не случится попасть на ожидание B, успешно при этом захватив A.
Из этого можно догадаться, простой способ гарантировать возможность дедлока – явно нарушить это условие.
Нарушение условия даст дедлок «скорее всего когда-нибудь». Чтобы получить его точно и с первого раза, нужно гарантировать, что оба потока окажутся на этапе между захватами одного и другого ресурса в одно время. Это можно сделать множеством способов, в примере ниже использован CyclicBarrier.
Java Guru🤓 #java
👍8🔥4❤1
Как устроены атомики?
Начнем с того, что такое атомики и зачем нужны. Atomic* – семейство классов из java.util.concurrent. Они предоставляют набор атомарных операций для соответствующих типов. Например с помощью методов getAndIncrement/incrementAndGet класса AtomicInteger можно делать неатомарный в обычных условиях инкремент (i++).
Условно можно разделить подходы реализации большинства atomic-методов на две группы: compare-and-set и set-and-get.
Методы категории compare-and-set принимают старое значение и новое. Если переданное старое значение совпало с текущим, устанавливается новое. Обычно делегируют вызов в методы класса Unsafe, которые заменяются нативными реализациями виртуальной машины. Виртуальная машина в большинстве случаев использует атомарную операцию процессора compare-and-swap (CAS). Поэтому атомики обычно более эффективны чем стандартная дорогостоящая блокировка.
В случае set-and-get старое значение неизвестно. Поэтому нужен небольшой трюк: программа сначала считывает текущее значение, а затем записывает новое, тоже с помощью CAS, потому что запись могла успеть поменяться даже за этот шаг. Эта попытка чтения+записи повторяется в цикле, пока старое значение не совпадет и переменная не будет успешно записана.
Этот трюк называется double-checked или optimistic locking, и может быть использован и в пользовательском коде с любым способом синхронизации. Оптимистичность заключается в том, что мы надеемся что состояния гонки нет, прибегая к синхронизации только если гонка всё же случилась. Реализация оптимистичной блокировки может быть дана как отдельная задача.
Java Guru🤓 #java
Начнем с того, что такое атомики и зачем нужны. Atomic* – семейство классов из java.util.concurrent. Они предоставляют набор атомарных операций для соответствующих типов. Например с помощью методов getAndIncrement/incrementAndGet класса AtomicInteger можно делать неатомарный в обычных условиях инкремент (i++).
Условно можно разделить подходы реализации большинства atomic-методов на две группы: compare-and-set и set-and-get.
Методы категории compare-and-set принимают старое значение и новое. Если переданное старое значение совпало с текущим, устанавливается новое. Обычно делегируют вызов в методы класса Unsafe, которые заменяются нативными реализациями виртуальной машины. Виртуальная машина в большинстве случаев использует атомарную операцию процессора compare-and-swap (CAS). Поэтому атомики обычно более эффективны чем стандартная дорогостоящая блокировка.
В случае set-and-get старое значение неизвестно. Поэтому нужен небольшой трюк: программа сначала считывает текущее значение, а затем записывает новое, тоже с помощью CAS, потому что запись могла успеть поменяться даже за этот шаг. Эта попытка чтения+записи повторяется в цикле, пока старое значение не совпадет и переменная не будет успешно записана.
Этот трюк называется double-checked или optimistic locking, и может быть использован и в пользовательском коде с любым способом синхронизации. Оптимистичность заключается в том, что мы надеемся что состояния гонки нет, прибегая к синхронизации только если гонка всё же случилась. Реализация оптимистичной блокировки может быть дана как отдельная задача.
Java Guru🤓 #java
👍8❤4🔥2
Что такое и как создать daemon thread?
Демон в широком значении – фоновая программа. В Java потоки-демоны имеют схожий смысл: это потоки для фоновых действий по обслуживанию основных потоков. Потоки не-демоны называются пользовательскими (user thread).
Тред создается демоном, если его родитель демон. Свойство Java-треда isDaemon можно переключать в любой момент до старта потока.
По сравнению с пользовательскими потоками демоны имеют меньший приоритет выполнения.
Когда все пользовательские треды завершились, JVM завершает работу. Демоны не выполняют самостоятельных задач, поэтому не препятствуют остановке, программа завершается не дожидаясь окончания их работы.
Daemon thread может быть полезен для таких действий, как инвалидация кэша, периодическая актуализация значений из внешних источников, освобождение неиспользуемых пользовательских ресурсов.
Java Guru🤓 #java
Демон в широком значении – фоновая программа. В Java потоки-демоны имеют схожий смысл: это потоки для фоновых действий по обслуживанию основных потоков. Потоки не-демоны называются пользовательскими (user thread).
Тред создается демоном, если его родитель демон. Свойство Java-треда isDaemon можно переключать в любой момент до старта потока.
По сравнению с пользовательскими потоками демоны имеют меньший приоритет выполнения.
Когда все пользовательские треды завершились, JVM завершает работу. Демоны не выполняют самостоятельных задач, поэтому не препятствуют остановке, программа завершается не дожидаясь окончания их работы.
Daemon thread может быть полезен для таких действий, как инвалидация кэша, периодическая актуализация значений из внешних источников, освобождение неиспользуемых пользовательских ресурсов.
Java Guru🤓 #java
🔥7👍5❤2
Зачем используются thread local переменные?
Класс ThreadLocal представляет хранилище тред-локальных переменных. По способу использования он похож на обычную обертку над значением, с методами get(), set() и remove() для доступа к нему, и дополнительным фабричным методом ThreadLocal.withInitial(), устанавливающим значение по-умолчанию.
Отличие тред-локальной переменной от обычной в том, что ThreadLocal хранит отдельную независимую копию значения для каждого ее использующего потока. Работа с такой переменной потокобезопасна.
Проще говоря, объект класса ThreadLocal хранит внутри не одно значение, а как бы хэш-таблицу поток➝значение, и при использовании обращается к значению для текущего потока.
Первый, самый очевидный вариант использования – данные, относящиеся непосредственно к треду, определенный пользователем «контекст потока». На скриншоте ниже пример такого использования: ThreadId.get() вернет порядковый номер текущего треда.
Другой случай, с которым локальная переменная потока может помочь – кэширование read-only данных в многопоточной среде без дорогостоящей синхронизации.
Помимо обычного ThreadLocal, в стандартной библиотеке присутствует его расширение InheritableThreadLocal. Этот класс «наследует» значение – изначально берет его для потока, являющегося родителем текущего.
Java Guru🤓 #java
Класс ThreadLocal представляет хранилище тред-локальных переменных. По способу использования он похож на обычную обертку над значением, с методами get(), set() и remove() для доступа к нему, и дополнительным фабричным методом ThreadLocal.withInitial(), устанавливающим значение по-умолчанию.
Отличие тред-локальной переменной от обычной в том, что ThreadLocal хранит отдельную независимую копию значения для каждого ее использующего потока. Работа с такой переменной потокобезопасна.
Проще говоря, объект класса ThreadLocal хранит внутри не одно значение, а как бы хэш-таблицу поток➝значение, и при использовании обращается к значению для текущего потока.
Первый, самый очевидный вариант использования – данные, относящиеся непосредственно к треду, определенный пользователем «контекст потока». На скриншоте ниже пример такого использования: ThreadId.get() вернет порядковый номер текущего треда.
Другой случай, с которым локальная переменная потока может помочь – кэширование read-only данных в многопоточной среде без дорогостоящей синхронизации.
Помимо обычного ThreadLocal, в стандартной библиотеке присутствует его расширение InheritableThreadLocal. Этот класс «наследует» значение – изначально берет его для потока, являющегося родителем текущего.
Java Guru🤓 #java
👍7🔥4❤2
Как выполнить две задачи параллельно?
Простейший, путь – явно создать два объекта типа Thread, передать им инстансы Runnable, с нужными задачами в реализации их методов run, и запустить вызвав thread.start(). Если в основном потоке нужно дождаться завершения задач – после start() вызывается метод thread.join(). Исполнение зависнет на вызове этого метода до тех пор, пока тред не закончит свою задачу и не умрет. Вся работа задач с внешними данными должна быть синхронизирована.
Такое ручное создание тредов полезно в учебных целях, но считается плохой практикой в промышленном коде: само создание – дорогостоящая операция, а большое количество случайно созданных потоков может приводить к проблеме голодания (starvation) потоков.
В качестве продвинутой альтернативы используются пуллы потоков – реализации интерфейса ExecutorService. Такие сервисы создаются статическими фабричными методами класса Executors. Они умеют принимать задачи в виде Runnable- или Callable-объектов на заранее созданном наборе потоков (собственно, пулле).
Кроме самого пулла, экземпляры ExecutorService содержат фабрику потоков («инструкцию» как создать тред при необходимости), и коллекцию-очередь задач на исполнение.
В ответ на передачу на исполнение Runnable или Callable, сервис возвращает связанный с ним объект типа Future – хранилище, которое будет заполнено результатом выполнения задачи в будущем. Даже если никакого результата не ожидается, Future поможет дождаться момента завершения обработки задачи.
В Android для асинхронного выполнения используется похожая сущность – Looper.
Java Guru🤓 #java
Простейший, путь – явно создать два объекта типа Thread, передать им инстансы Runnable, с нужными задачами в реализации их методов run, и запустить вызвав thread.start(). Если в основном потоке нужно дождаться завершения задач – после start() вызывается метод thread.join(). Исполнение зависнет на вызове этого метода до тех пор, пока тред не закончит свою задачу и не умрет. Вся работа задач с внешними данными должна быть синхронизирована.
Такое ручное создание тредов полезно в учебных целях, но считается плохой практикой в промышленном коде: само создание – дорогостоящая операция, а большое количество случайно созданных потоков может приводить к проблеме голодания (starvation) потоков.
В качестве продвинутой альтернативы используются пуллы потоков – реализации интерфейса ExecutorService. Такие сервисы создаются статическими фабричными методами класса Executors. Они умеют принимать задачи в виде Runnable- или Callable-объектов на заранее созданном наборе потоков (собственно, пулле).
Кроме самого пулла, экземпляры ExecutorService содержат фабрику потоков («инструкцию» как создать тред при необходимости), и коллекцию-очередь задач на исполнение.
В ответ на передачу на исполнение Runnable или Callable, сервис возвращает связанный с ним объект типа Future – хранилище, которое будет заполнено результатом выполнения задачи в будущем. Даже если никакого результата не ожидается, Future поможет дождаться момента завершения обработки задачи.
В Android для асинхронного выполнения используется похожая сущность – Looper.
Java Guru🤓 #java
👍9🔥5❤4
Что происходит если не обработать исключение?
Если не было предпринято дополнительных действий, в этой ситуации нет никаких хитростей. Всё приложение, и даже метод main(), выполняется в потоках. Поток, в котором было выброшено и не обработано исключение, остановится, и распечатает стектрейс в вывод System.err. Если это был последний пользовательский поток, приложение начнет завершение работы.
Для изменения логики обработки непойманных исключений в Java существует функциональный интерфейс Thread.UncaughtExceptionHandler.
Обработчик упущенных исключений может быть установлен (в порядке возрастания приоритета):
• глобально на всё приложение, статическим методом Thread.setDefaultUncaughtExceptionHandler();
• для группы потоков, переопределением метода uncaughtException() в реализации объекта подкласса ThreadGroup (т.к. ThreadGroup сам является наследником UncaughtExceptionHandler);
• для отдельного потока, методом setUncaughtExceptionHandler().
Естественно, установка нестандартного обработчика не имеет обратной силы. Используя его, нужно убедиться, что он установлен достаточно рано, до выброса какого-либо исключения.
Хорошей практикой считается обрабатывать исключение настолько близко к месту его выброса, насколько возможно. Следовательно, использование глобальных обработчиков – самый плохой вариант.
Так же как в случае различных финализаций, несмотря на все её недостатки, глобальная обработка иногда лучше, чем ничего. Она может, например, дать последний шанс освободить внешние ресурсы, или уведомить о некорректной работе программы более эффективно, чем через логи.
Когда код с исключением выполняет ExecutorService, мы не имеем прямого доступа к объектам потока. Но в этом случае результатом выполнения будет объект типа Future. Такой отложенный объект при попытке прочитать значение перевыбросит полученное исключение, завернув его в ExecutionException. Новое исключение-обертка уже пойдет по обычному пути обработки текущего потока. Исключение как бы перекочует из внутреннего потока пулла во внешний, который использует этот пулл.
Если же пользовательский код не станет дожидаться результатов, исключение будет потеряно, не оставив даже стектрейса в потоке вывода. Для предотвращения такой ситуации стоит снабдить поток обработчиком сразу после создания, определив для сервиса собственную ThreadFactory.
Обычно, если фреймворк скрывает от пользователя детали работы с потоками, он также скрывает и детали работы с исключениями, оставляя свой специальный способ назначить обработчик. И этот специальный обработчик – более специфичный, а значит более правильный подход, чем стандартная глобальная обработка исключений Java. Так, например, в Spring MVC применяется аннотация @ExceptionHandler.
Java Guru🤓 #java
Если не было предпринято дополнительных действий, в этой ситуации нет никаких хитростей. Всё приложение, и даже метод main(), выполняется в потоках. Поток, в котором было выброшено и не обработано исключение, остановится, и распечатает стектрейс в вывод System.err. Если это был последний пользовательский поток, приложение начнет завершение работы.
Для изменения логики обработки непойманных исключений в Java существует функциональный интерфейс Thread.UncaughtExceptionHandler.
Обработчик упущенных исключений может быть установлен (в порядке возрастания приоритета):
• глобально на всё приложение, статическим методом Thread.setDefaultUncaughtExceptionHandler();
• для группы потоков, переопределением метода uncaughtException() в реализации объекта подкласса ThreadGroup (т.к. ThreadGroup сам является наследником UncaughtExceptionHandler);
• для отдельного потока, методом setUncaughtExceptionHandler().
Естественно, установка нестандартного обработчика не имеет обратной силы. Используя его, нужно убедиться, что он установлен достаточно рано, до выброса какого-либо исключения.
Хорошей практикой считается обрабатывать исключение настолько близко к месту его выброса, насколько возможно. Следовательно, использование глобальных обработчиков – самый плохой вариант.
Так же как в случае различных финализаций, несмотря на все её недостатки, глобальная обработка иногда лучше, чем ничего. Она может, например, дать последний шанс освободить внешние ресурсы, или уведомить о некорректной работе программы более эффективно, чем через логи.
Когда код с исключением выполняет ExecutorService, мы не имеем прямого доступа к объектам потока. Но в этом случае результатом выполнения будет объект типа Future. Такой отложенный объект при попытке прочитать значение перевыбросит полученное исключение, завернув его в ExecutionException. Новое исключение-обертка уже пойдет по обычному пути обработки текущего потока. Исключение как бы перекочует из внутреннего потока пулла во внешний, который использует этот пулл.
Если же пользовательский код не станет дожидаться результатов, исключение будет потеряно, не оставив даже стектрейса в потоке вывода. Для предотвращения такой ситуации стоит снабдить поток обработчиком сразу после создания, определив для сервиса собственную ThreadFactory.
Обычно, если фреймворк скрывает от пользователя детали работы с потоками, он также скрывает и детали работы с исключениями, оставляя свой специальный способ назначить обработчик. И этот специальный обработчик – более специфичный, а значит более правильный подход, чем стандартная глобальная обработка исключений Java. Так, например, в Spring MVC применяется аннотация @ExceptionHandler.
Java Guru🤓 #java
👍9❤6🔥5
Как реализовать паттерн producer/consumer?
Шаблон producer/consumer (производитель/потребитель) – простая и базовая реализация обмена данными между несколькими потоками. Поток-производитель отправляет объекты на условную обработку, потоки-потребители асинхронно принимают и обрабатывают их.
Общий вид решения выглядит так. Продюсер отправляет объекты в специальную коллекцию – буфер. Когда потребитель освобождается, он отправляет запрос на извлечение одного объекта из буфера. Если буфер пуст, потребитель блокируется и ждет, если буфер переполнен – ждет производитель.
На практике реализовать этот паттерн можно множеством способов. Самый правильный способ для применения в бою – использовать готовую реализацию из стандартной библиотеки, объект типа BlockingQueue.
На собеседовании обычно просят реализовать паттерн с нуля. Реализация представлена на изображении. Модификатор synchronized делает так, чтобы в каждый момент времени мог выполняться только один из методов, и только одним потоком. Этого достаточно для корректной работы пока буфер не пуст и не полон. При пустом или полном буфере управление явно перебрасывается на производителя или потребителя соответственно, с помощью методов notify() и wait().
Шаблону producer/consumer посвящена глава 5.3 книги Java Concurrency in Practice.
Сильно упрощая, на основе этого паттерна работают сервисы-брокеры сообщений: Rabbit MQ, Apache ActiveMQ и другие.
Java Guru🤓 #java
Шаблон producer/consumer (производитель/потребитель) – простая и базовая реализация обмена данными между несколькими потоками. Поток-производитель отправляет объекты на условную обработку, потоки-потребители асинхронно принимают и обрабатывают их.
Общий вид решения выглядит так. Продюсер отправляет объекты в специальную коллекцию – буфер. Когда потребитель освобождается, он отправляет запрос на извлечение одного объекта из буфера. Если буфер пуст, потребитель блокируется и ждет, если буфер переполнен – ждет производитель.
На практике реализовать этот паттерн можно множеством способов. Самый правильный способ для применения в бою – использовать готовую реализацию из стандартной библиотеки, объект типа BlockingQueue.
На собеседовании обычно просят реализовать паттерн с нуля. Реализация представлена на изображении. Модификатор synchronized делает так, чтобы в каждый момент времени мог выполняться только один из методов, и только одним потоком. Этого достаточно для корректной работы пока буфер не пуст и не полон. При пустом или полном буфере управление явно перебрасывается на производителя или потребителя соответственно, с помощью методов notify() и wait().
Шаблону producer/consumer посвящена глава 5.3 книги Java Concurrency in Practice.
Сильно упрощая, на основе этого паттерна работают сервисы-брокеры сообщений: Rabbit MQ, Apache ActiveMQ и другие.
Java Guru🤓 #java
🔥11👍6❤3
🗓 17 июля в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java-разработчик».
Ошибки в кодировках ломают приложения, превращая текст в «абракадабру». Понимание таблиц кодировок — must-have навык для работы с данными, файлами и международными проектами.
О чём поговорим:
Кому будет интересно:
Начинающим Java-разработчикам и тестировщикам, сталкивающимся с международными данными и проблемами кодировки.
В результате вебинара вы:
Научитесь контролировать кодировки в своих приложениях, исправлять «кракозябры» и работать с любыми текстовыми данными без страха.
🔗 Ссылка на регистрацию: https://vk.cc/cNvfEE
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3👍2🔥2
Что такое ForkJoinPool?
ForkJoinPool – специальный вид ExecutorService (пулла потоков), который появился в Java с версии 7. Предназначен для выполнения рекурсивных задач.
Задача для сервиса представляется экземпляром класса ForkJoinTask. В основном используются подклассы RecursiveTask и RecursiveAction, для задач с результатом и без соответственно. Аналогично интерфейсам Callable и Runnable обычного ExecutorService.
Тело рекурсивной операции задается в реализации метода compute() задачи ForkJoinTask. Здесь же создаются новые подзадачи, и запускаются параллельно методом fork(). Чтобы дождаться завершения выполнения задачи, на каждой форкнутой подзадаче вызывается блокирующий метод join(), результат выполнения при необходимости агрегируется.
С точки зрения использования метод ForkJoinTask.join() похож на аналогичный метод класса Thread. Но в случае fork-join поток может на самом деле не заснуть, а переключиться на выполнение другой задачи. Такая стратегия называется work stealing, и позволяет эффективнее использовать ограниченное количество потоков. Это похоже на переиспользование потоков корутинах Kotlin (green threads).
Примеры практического использования ForkJoinPool.
Java Guru🤓 #java
ForkJoinPool – специальный вид ExecutorService (пулла потоков), который появился в Java с версии 7. Предназначен для выполнения рекурсивных задач.
Задача для сервиса представляется экземпляром класса ForkJoinTask. В основном используются подклассы RecursiveTask и RecursiveAction, для задач с результатом и без соответственно. Аналогично интерфейсам Callable и Runnable обычного ExecutorService.
Тело рекурсивной операции задается в реализации метода compute() задачи ForkJoinTask. Здесь же создаются новые подзадачи, и запускаются параллельно методом fork(). Чтобы дождаться завершения выполнения задачи, на каждой форкнутой подзадаче вызывается блокирующий метод join(), результат выполнения при необходимости агрегируется.
С точки зрения использования метод ForkJoinTask.join() похож на аналогичный метод класса Thread. Но в случае fork-join поток может на самом деле не заснуть, а переключиться на выполнение другой задачи. Такая стратегия называется work stealing, и позволяет эффективнее использовать ограниченное количество потоков. Это похоже на переиспользование потоков корутинах Kotlin (green threads).
Примеры практического использования ForkJoinPool.
Java Guru🤓 #java
👍9🔥5❤4
Чем ForkJoinPool отличается от ExecutorService?
ForkJoinPool сам по себе является наследником ExecutorService. Вопрос подразумевает его отличия от обычного пула потоков – ThreadPoolExecutor.
Преимущества, которые дает work stealing по сравнению с обычным пулом:
• Сокращение расходов на переключение контекста;
• Защита от проблемы голодания потоков (thread starvation);
• Защита от дедлока для рекурсивных задач.
Как положено любому представителю ExecutorService, ForkJoinPool тоже умеет выполнять Runnable и Callable, но помимо этого работает и со специальными задачами ForkJoinTask, о которых также говорилось ранее.
Интерфейс настройки и мониторинга остается тем же, что и в классических тред-пулах.
Каждый обычный пул использует собственный набор потоков. ForkJoinPool по умолчанию использует общий пул-синглтон commonPool. Альтернативный отдельный пул всё еще можно задать в конструкторе.
ForkJoinPool сам регулирует количество запущенных потоков, достигая максимальной эффективности при заданном уровне параллелизма.
Java Guru🤓 #java
ForkJoinPool сам по себе является наследником ExecutorService. Вопрос подразумевает его отличия от обычного пула потоков – ThreadPoolExecutor.
Преимущества, которые дает work stealing по сравнению с обычным пулом:
• Сокращение расходов на переключение контекста;
• Защита от проблемы голодания потоков (thread starvation);
• Защита от дедлока для рекурсивных задач.
Как положено любому представителю ExecutorService, ForkJoinPool тоже умеет выполнять Runnable и Callable, но помимо этого работает и со специальными задачами ForkJoinTask, о которых также говорилось ранее.
Интерфейс настройки и мониторинга остается тем же, что и в классических тред-пулах.
Каждый обычный пул использует собственный набор потоков. ForkJoinPool по умолчанию использует общий пул-синглтон commonPool. Альтернативный отдельный пул всё еще можно задать в конструкторе.
ForkJoinPool сам регулирует количество запущенных потоков, достигая максимальной эффективности при заданном уровне параллелизма.
Java Guru🤓 #java
👍9🔥4❤2
15 июля в 20:00 МСК OTUS проведёт открытый урок «Нормальная денормализация» — ключевой приём для оптимизации доступа к данным в NoSQL.
На примере Spring Data MongoDB разберём, как настраивать связи между сущностями: когда выбрать вложенные документы, а когда — ссылочные связи. Вы поймёте, как денормализация влияет на производительность запросов, расходы на память и сложность поддержки.
Урок будет полезен Java-разработчикам, backend-инженерам и архитекторам, работающим с MongoDB. Вы получите готовые шаблоны организации данных в Spring-приложениях, избежите типичных ошибок при проектировании схемы и сможете принимать обоснованные архитектурные решения.
Присоединяйтесь к уроку и получите скидку на полный курс «Разработчик на Spring Framework».
Регистрируйтесь прямо сейчас: https://vk.cc/cNzFo6
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
На примере Spring Data MongoDB разберём, как настраивать связи между сущностями: когда выбрать вложенные документы, а когда — ссылочные связи. Вы поймёте, как денормализация влияет на производительность запросов, расходы на память и сложность поддержки.
Урок будет полезен Java-разработчикам, backend-инженерам и архитекторам, работающим с MongoDB. Вы получите готовые шаблоны организации данных в Spring-приложениях, избежите типичных ошибок при проектировании схемы и сможете принимать обоснованные архитектурные решения.
Присоединяйтесь к уроку и получите скидку на полный курс «Разработчик на Spring Framework».
Регистрируйтесь прямо сейчас: https://vk.cc/cNzFo6
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
👍3🔥3❤2
Что будет результатом кода?
Anonymous Quiz
4%
0
7%
1
50%
2
8%
Ошибка компиляции
31%
Невозможно предсказать
1👍8🔥5
LangChain открывает мощные возможности LLM в приложениях Java, упрощая интеграцию ИИ в сервисы на Quarkus и Spring Boot.
🗓 17 июня в 20:00 МСК
🆓 Бесплатно. Урок в рамках старта курса «Java Developer. Advanced».
- Framework LangChain: ключевые концепции и архитектура.
- Langchain4j: возможности Java-библиотеки.
- Интеграция Langchain4j в проекты на Spring Boot и Quarkus.
- Spring AI: фреймворк для работы с LLM в Spring.
Java-разработчикам, архитекторам ПО и инженерам ML Ops, планирующим внедрять LLM в микросервисы на Quarkus или Spring Boot.
Познакомитесь с Langchain4j и сможете написать простой Java-сервис, использующий LLM.
🔗 Ссылка на регистрацию: https://vk.cc/cNzVUK
Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
Please open Telegram to view this post
VIEW IN TELEGRAM
1❤3🔥3👍2
Что будет результатом кода?
Anonymous Quiz
18%
10 целых случайных чисел от 1 до 10
10%
10 целых одинаковых случайных чисел
7%
10 дробных случайных чисел
51%
10 целых случайных чисел
13%
ошибка компиляции
👍11🔥4❤3
Как работают параллельные стримы?
Основная цель, ради которой в Java 8 был добавлен Stream API – удобство многопоточной обработки.
Обычный стрим будет выполняться параллельно после вызова промежуточной операции parallel(). Некоторые стримы создаются уже многопоточными, например результат вызова Collection#parallelStream(). Для распараллеливания используется единый общий ForkJoinPool.
Внутри реализации потока его сплиттератор оборачивается в AbstractTask, который и отправляется на выполнение в пул. AbstractTask при выполнении считывает estimateSize сплиттератора и текущую степень параллелизма пула. На основе этих данных он принимает решение, распараллелить ли сплиттератор на два методом trySplit().
У удобства такого решения есть обратная сторона. Так как пул единый, нагрузка распределяется на всех пользователей параллельных стримов в программе. Если в одном потоке выполняются долгие блокирующие операции, это может ударить по производительности в совершенно не связанном с ним другом потоке.
Если всё же требуется использовать отдельный пул потоков, сам стрим выполняется как задача этого отдельного пула.
Java Guru🤓 #java
Основная цель, ради которой в Java 8 был добавлен Stream API – удобство многопоточной обработки.
Обычный стрим будет выполняться параллельно после вызова промежуточной операции parallel(). Некоторые стримы создаются уже многопоточными, например результат вызова Collection#parallelStream(). Для распараллеливания используется единый общий ForkJoinPool.
Внутри реализации потока его сплиттератор оборачивается в AbstractTask, который и отправляется на выполнение в пул. AbstractTask при выполнении считывает estimateSize сплиттератора и текущую степень параллелизма пула. На основе этих данных он принимает решение, распараллелить ли сплиттератор на два методом trySplit().
У удобства такого решения есть обратная сторона. Так как пул единый, нагрузка распределяется на всех пользователей параллельных стримов в программе. Если в одном потоке выполняются долгие блокирующие операции, это может ударить по производительности в совершенно не связанном с ним другом потоке.
Если всё же требуется использовать отдельный пул потоков, сам стрим выполняется как задача этого отдельного пула.
Java Guru🤓 #java
👍10🔥5
🔥 БЕСПЛАТНЫЙ КУРС ПО СОЗДАНИЮ НЕЙРО-СОТРУДНИКОВ НА GPT И ДРУГИХ LLM 🔥
Ищете практический и углубленный курс, чтобы освоить создание нейро-сотрудников? Мы создали курс из 5 объемных занятий. Это именно то, что нужно, чтобы прокачать свои навыки абсолютно бесплатно!
📌 Темы занятий:
1. Введение в мир нейро-сотрудников
2. Как работают LLM и их аналоги
3. Создание базы знаний для нейро-сотрудника (RAG)
4. Тестирование и отладка нейро-сотрудников
5. Интеграция нейро-сотрудников в Production
Вот 5 тем курса - он максимально простой и доступный, общеобразовательный, без какого-либо сложного программирования 📚Прохождение этого курса, скорее всего, займет у вас от 1 до 3 часов
🤖 Присоединяйтесь к нашему бесплатному курсу и разберитесь в этой увлекательной теме с нами!
Ищете практический и углубленный курс, чтобы освоить создание нейро-сотрудников? Мы создали курс из 5 объемных занятий. Это именно то, что нужно, чтобы прокачать свои навыки абсолютно бесплатно!
📌 Темы занятий:
1. Введение в мир нейро-сотрудников
2. Как работают LLM и их аналоги
3. Создание базы знаний для нейро-сотрудника (RAG)
4. Тестирование и отладка нейро-сотрудников
5. Интеграция нейро-сотрудников в Production
Вот 5 тем курса - он максимально простой и доступный, общеобразовательный, без какого-либо сложного программирования 📚Прохождение этого курса, скорее всего, займет у вас от 1 до 3 часов
🤖 Присоединяйтесь к нашему бесплатному курсу и разберитесь в этой увлекательной теме с нами!
👍3❤1
Чем CompletableFuture отличается от Future?
Future – интерфейс, который представляет пока еще недовычисленный результат. Когда породившая его асинхронная операция заканчивается, он заполняется значением. Метод get блокирует выполнение до получения результата, isDone проверяет его наличие. К примеру результат выполнения задач в ExecutorService, ForkJoinTask, реализует интерфейс Future.
CompletableFuture появился в Java 8. Это класс-реализация старого интерфейса Future, а значит всё сказанное выше справедливо и для него. Вдобавок к этому, CompletableFuture реализует работу с отложенными результатами посредством коллбэков. Метод thenApply регистрирует код обработки значения, который будет автоматически вызван позже, когда это значение появится.
В Java 9 прогресс пошел дальше, и появилась библиотека Flow API. Это встроенная реализация реактивных стримов. Реактивный стрим, сильно упрощая, – это более общий случай, последовательность отложенных значений. Другая их реализация – популярная, но не входящая в стандарт библиотека Reactive Extensions (RxJava).
Java Guru🤓 #java
Future – интерфейс, который представляет пока еще недовычисленный результат. Когда породившая его асинхронная операция заканчивается, он заполняется значением. Метод get блокирует выполнение до получения результата, isDone проверяет его наличие. К примеру результат выполнения задач в ExecutorService, ForkJoinTask, реализует интерфейс Future.
CompletableFuture появился в Java 8. Это класс-реализация старого интерфейса Future, а значит всё сказанное выше справедливо и для него. Вдобавок к этому, CompletableFuture реализует работу с отложенными результатами посредством коллбэков. Метод thenApply регистрирует код обработки значения, который будет автоматически вызван позже, когда это значение появится.
В Java 9 прогресс пошел дальше, и появилась библиотека Flow API. Это встроенная реализация реактивных стримов. Реактивный стрим, сильно упрощая, – это более общий случай, последовательность отложенных значений. Другая их реализация – популярная, но не входящая в стандарт библиотека Reactive Extensions (RxJava).
Java Guru🤓 #java
🔥12👍6
Зачем выбирать ReentrantLock вместо synchronized?
Объект класса ReentrantLock решает те же задачи, что и блок synchronized. Поток висит на вызове метода lock() в ожидании своей очереди занять этот объект. Владеть локом, как и находиться внутри блока synchronized может только один поток одновременно. unlock(), подобно выходу из блока синхронизации, освобождает объект-монитор для других потоков.
В отличие от блока синхронизации, ReentrantLock дает расширенный интерфейс для получения информации о состоянии блокировки. Методы лока позволяют еще до блокировки узнать, занят ли он сейчас, сколько потоков ждут его в очереди, сколько раз подряд текущий поток завладел им.
Шире и возможные режимы блокировки. Кроме обычного ожидающего lock(), вариант tryLock() с параметром ожидает своей очереди только заданное время, а без параметра – вообще не ждет, а только захватывает свободный лок.
Еще одно отличие – свойство fair. Лок с этим свойством обеспечивает «справедливость» очереди: пришедший раньше поток захватывает объект раньше. Блок synchronized не дает никаких гарантий порядка.
Java Guru🤓 #java
Объект класса ReentrantLock решает те же задачи, что и блок synchronized. Поток висит на вызове метода lock() в ожидании своей очереди занять этот объект. Владеть локом, как и находиться внутри блока synchronized может только один поток одновременно. unlock(), подобно выходу из блока синхронизации, освобождает объект-монитор для других потоков.
В отличие от блока синхронизации, ReentrantLock дает расширенный интерфейс для получения информации о состоянии блокировки. Методы лока позволяют еще до блокировки узнать, занят ли он сейчас, сколько потоков ждут его в очереди, сколько раз подряд текущий поток завладел им.
Шире и возможные режимы блокировки. Кроме обычного ожидающего lock(), вариант tryLock() с параметром ожидает своей очереди только заданное время, а без параметра – вообще не ждет, а только захватывает свободный лок.
Еще одно отличие – свойство fair. Лок с этим свойством обеспечивает «справедливость» очереди: пришедший раньше поток захватывает объект раньше. Блок synchronized не дает никаких гарантий порядка.
Java Guru🤓 #java
👍13🔥6❤2