Java Guru 🤓
13.2K subscribers
704 photos
14 videos
587 links
Канал с вопросами и задачами с собеседований!

По сотрудничеству и рекламе: @NadikaKir

Канал в перечне РКН: https://vk.cc/cJrSQZ

Мы на бирже: telega.in/channels/javatasks/card?r=lcDuijdm
Download Telegram
Как реализовать паттерн producer/consumer?

Шаблон producer/consumer (производитель/потребитель) – простая и базовая реализация обмена данными между несколькими потоками. Поток-производитель отправляет объекты на условную обработку, потоки-потребители асинхронно принимают и обрабатывают их.

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

На практике реализовать этот паттерн можно множеством способов. Самый правильный способ для применения в бою – использовать готовую реализацию из стандартной библиотеки, объект типа BlockingQueue.

На собеседовании обычно просят реализовать паттерн с нуля. Реализация представлена на изображении. Модификатор synchronized делает так, чтобы в каждый момент времени мог выполняться только один из методов, и только одним потоком. Этого достаточно для корректной работы пока буфер не пуст и не полон. При пустом или полном буфере управление явно перебрасывается на производителя или потребителя соответственно, с помощью методов notify() и wait().

Шаблону producer/consumer посвящена глава 5.3 книги Java Concurrency in Practice.

Сильно упрощая, на основе этого паттерна работают сервисы-брокеры сообщений: Rabbit MQ, Apache ActiveMQ и другие.


Подписывайся на наш канал в Max 🟪
Please open Telegram to view this post
VIEW IN TELEGRAM
👍13🔥61🤔1
Какая NoSQL база данных чаще всего используется для хранения документов?
Anonymous Quiz
14%
Cassandra
2%
Neo4j
74%
MongoDB
1%
InfluxDB
9%
Redis
👍5🔥3😐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.

Подписывайся на наш канал в Max 🟪
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🔥2
Чем ForkJoinPool отличается от ExecutorService?

ForkJoinPool сам по себе является наследником ExecutorService. Вопрос подразумевает его отличия от обычного пула потоков – ThreadPoolExecutor.

Преимущества, которые дает work stealing по сравнению с обычным пулом:
• Сокращение расходов на переключение контекста;
• Защита от проблемы голодания потоков (thread starvation);
• Защита от дедлока для рекурсивных задач.

Как положено любому представителю ExecutorService, ForkJoinPool тоже умеет выполнять Runnable и Callable, но помимо этого работает и со специальными задачами ForkJoinTask, о которых также говорилось ранее.

Интерфейс настройки и мониторинга остается тем же, что и в классических тред-пулах.

Каждый обычный пул использует собственный набор потоков. ForkJoinPool по умолчанию использует общий пул-синглтон commonPool. Альтернативный отдельный пул всё еще можно задать в конструкторе.

ForkJoinPool сам регулирует количество запущенных потоков, достигая максимальной эффективности при заданном уровне параллелизма.


Подписывайся на наш канал в Max 🟪
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥32👍21🤨1
Как работают параллельные стримы?

Основная цель, ради которой в Java 8 был добавлен Stream API – удобство многопоточной обработки.

Обычный стрим будет выполняться параллельно после вызова промежуточной операции parallel(). Некоторые стримы создаются уже многопоточными, например результат вызова Collection#parallelStream(). Для распараллеливания используется единый общий ForkJoinPool.

Внутри реализации потока его сплиттератор оборачивается в AbstractTask, который и отправляется на выполнение в пул. AbstractTask при выполнении считывает estimateSize сплиттератора и текущую степень параллелизма пула. На основе этих данных он принимает решение, распараллелить ли сплиттератор на два методом trySplit().

У удобства такого решения есть обратная сторона. Так как пул единый, нагрузка распределяется на всех пользователей параллельных стримов в программе. Если в одном потоке выполняются долгие блокирующие операции, это может ударить по производительности в совершенно не связанном с ним другом потоке.

Если всё же требуется использовать отдельный пул потоков, сам стрим выполняется как задача этого отдельного пула.


Подписывайся на наш канал в Max 🟪
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥5👍21
Как инстанцировать экземпляр generic типа?

Внутри класса class Foo<T> на generic параметре T невозможно выполнить никакой оператор: нельзя взять его .class, нельзя применить его в instanceof. Также и вызов на нем оператора new приведет к ошибке.

Причина этих ограничений кроется в стирании типов. Дженерик параметры правильно воспринимать скорее как ограничения типов, чем как конкретные типы. Эти ограничения действуют для более строгих проверок на этапе компиляции. В рантайме же информация о конкретных переданных типах-параметрах стирается. А все эти операторы выполняются именно в рантайме.

Стандартный простой способ действия здесь – кроме значения типа T передавать еще и объект-дескриптор для этого типа, экземпляр класса Class<T>. Объект может быть создан из дескриптора рефлекшеном.

Но существует один хак, способный справиться со стиранием типов. Тип-параметр все-таки остается в одном месте в рантайме. Метод метакласса наследника определившего конкретный тип getGenericSuperclass() возвращает класс, которым параметризован родитель.


Подписывайся на наш канал в Max 🟪
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥7👍42
Как написать иммутабельный класс?

Immutable (неизменяемый) класс – это класс, состояние экземпляров которого невозможно изменить после создания.

С иммутабельным классом всегда легче работать. Его состояние не поменяется, значит обращаться к нему в многопоточной среде можно без дополнительной синхронизации. Функции, зависящие только от состояния экземпляра будут возвращать один и тот же результат от вызова к вызову – это облегчает например реализацию hashCode(). Также вместо нескольких одинаковых экземпляров можно использовать один закэшированный объект, экономя память (паттерн Приспособленец).

Шаги, которые необходимо предпринять, чтобы класс стал immutable:

1. Запретите расширение класса – либо объявите его final, либо закройте доступ наследникам ко всем способам мутации, перечисленным в следующих пунктах;
2. Сделайте все поля финальными;
3. Не выставляйте наружу методов-мутаторов, которые меняют состояние;
4. Не отдавайте наружу поля ссылочного изменяемого типа (объекты классов, массивы) – если объект под ссылкой не иммутабельный, должна возвращаться его глубокая копия (defensive copy);
5. Создавайте объект правильно (подробнее в следующем посте).

Если вам нужны преимущества иммутабельного объекта, но также нужно иногда изменять его, подойдет подход copy on write: каждый метод-мутатор должен мутировать и возвращать не сам объект, а только что созданную его копию. Оригинал всё так же остается неизменным.


Подписывайся на наш канал в Max 🟪
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥4
Как реализовать метод equals?

Сначала нужно решить, действительно ли вам нужно переопределять equals(). Реализация по умолчанию делает объект равным только самому себе (сравнение на идентичность). Это имеет смысл, если у вашего класса не бывает отдельных, но логически одинаковых экземпляров.

Если два экземпляра всё-таки могут быть равны, equals() нужно переопределять. Реализация должна соблюдать контракт: это отношение эквивалентности (рефлексивность, транзитивность, симметричность), ни один объект не равен null.

Рефлексивность. первым делом проверим, не идентичен ли переданный объект текущему. Если да – сразу вернем true.

Неравенство null. Если аргумент null – сразу вернем false.

Симметричность. Если мы допускаем наследование и расширение метода equals(), в наследнике может появиться дополнительная логика, которая сделает !other.equals(this) при this.equals(other). Проще всего избежать этого, добавив сравнение типов. Если типы не равны – сразу вернем false. Почему не надо использовать instanceof.

Транзитивность. Оператор == обладает свойствами транзитивности и симметричности. Далее мы сравниваем на равенство все примитивные свойства. Для ссылочных типов этими характеристиками по контракту обладает equals – для сравнения ссылочных типов пользуемся им.

Речь здесь идет о логических свойствах. Фактически одно логическое свойство может быть представлено несколькими полями класса, или же может вычисляться на лету. Некоторые поля служат для внутренних технических нужд, и не имеют отношения к логическому состоянию. Такие поля обычно исключают из сравнения.


Подписывайся на наш канал в Max 🟪
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🔥42
👩‍💻 Открытый урок «Реализация простого HTTP сервера на Java»

🗓 10 марта в 20:00 МСК

🆓 Бесплатно. Урок в рамках старта курса «Java-разработчик» от Otus.

Одно из основных направлений в Java разработке – разработка веб-приложений. Любому веб-разработчику важно понимать, что из себя представляет протокол HTTP и как внутри работает HTTP-сервер.

О чем поговорим:

✔️ Что из себя представляет протокол HTTP и как внутри работает HTTP-сервер
✔️ Как с помощью штатных средств языка Java (без использования сторонних библиотек) разработать простой HTTP сервер
✔️ Вопросы возможности экономии ресурсов системы на обработку каждого запроса

Кому будет интересно:
Вебинар будет полезен тем, у кого есть уверенные знания в базе языка Java и ООП; желательно понимание базовых принципов работы с потоками ввода/вывода (java.io) и многопоточностью.


🔗 Ссылка на регистрацию: https://vk.cc/cV0AlJ

Реклама. ООО «Отус онлайн-образование», ОГРН 1177746618576
3👍2🔥2
Как реализовать метод hashCode?

Если вы переопределили equals(), то обязательно также переопределить и hashCode(). Это не просто теоретическое требование. Если класс нарушает это правило, хранение его экземпляров в качестве например ключей HashMap приводит к непредсказуемому поведению.

Результат hashCode() должен быть одинаковый для равных в смысле equals объектов. Обычно для этого значение хэш-кода вычисляется на основе значений полей, которые участвуют в equals(). Но и возвращение одной и той же константы 42 для любого экземпляра класса тоже будет валидной реализацией.

Результат hashCode() должен быть равномерно распределен. Это правило не такое строгое как остальные. Его нарушение не сломает программу, хотя может сильно ухудшить производительность. Поэтому константа 42 – допустимая, но не лучшая идея. Вместо этого все значения полей сначала приводятся к int: boolean превращается в любую пару констант, null в 0, для ссылочных типов берется их hashCode(). Затем все эти значения смешиваются с помощью бинарного оператора XOR (^). Дополнительно для лучшего распределения можно применять битовые сдвиги. Если вы владеете информацией о распределении значений полей в конкретно вашем случае, эту реализацию можно улучшить.

Результат hashCode() должен быть одинаковый на протяжении времени жизни объекта. Если вычисление хэш-кода зависит от переменных значений, сохраните его значение во внутреннее поле при первом вызове. При следующих вызовах сразу возвращайте это закэшированное значение.


Подписывайся на наш канал в Max 🟪
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥1
🔴 КАК ИДЕАЛЬНО ПРОЙТИ СОБЕС? ПОКАЖЕМ ЗАВТРА!

Каждый, проходя интервью, думал: «Ну что они хотят услышать? Я же правильно ответил! Почему меня не взяли?»

4 марта(уже завтра!) в 19:00 по мск приходи онлайн на открытое интервью, где будут собеседовать МЕНТОРА ШОРТКАТ

Как это будет:
📂 Виктор Анохин, старший разработчик из WildBerries, будет задавать реальные вопросы и задачи старшему разработчику Сергею Чамкину
📂 Сергей будет отвечать на каждый вопрос так, как это ожидает сам от вас на собеседованиях
📂 В конце можно будет задать любой вопрос Сергею и Виктору

Это бесплатно. Эфир проходит в рамках менторской программы от ШОРТКАТ для Java-разработчиков, которые хотят повысить свой грейд, ЗП и прокачать скиллы.

Переходи в нашего бота, чтобы получить ссылку на эфир →
@shortcut_sh_bot

Реклама.
О рекламодателе.
Please open Telegram to view this post
VIEW IN TELEGRAM
2👍2🔥2
Чем отличается Closeable от AutoCloseable?

Интерфейс AutoCloseable представляет объект-хранилище некоего ресурса, пока тот не закрыт. В единственном его методе close() объявляется логика закрытия этого ресурса. Пример – дескриптор открытого файла (ObjectOutputStream).

Особенность этого интерфейса в том, что его применение позволяет использовать объект в языковой конструкции try-with-resource. Всё это появилось в Java версии 7.

До Java 7 уже существовал похожий интерфейс – Closeable. Смысл его точно такой же. Он всё еще доступен в стандартной библиотеке для обратной совместимости, но в новом коде рекомендуется использовать AutoCloseable. Чтобы экземпляры старого Closeable тоже можно было использовать в try-with-resource, новый интерфейс был добавлен его родителем.

Проблема старого интерфейса Closeable была в узости типа исключений, которые может выбрасывать close(). Ковариантность позволила расширить тип в базовом интерфейсе AutoCloseable с IOException до Exception.

Еще одно отличие – контракт метода close(). Старый Closeable требует его идемпотентности, тогда как новый AutoCloseable разрешает методу иметь побочные эффекты.


Подписывайся на наш канал в Max 🟪
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6👍3
Как в лямбде изменить внешнюю локальную переменную?

Это нельзя сделать в лоб. Такой код не скомпилируется, потому что захваченная локальная переменная обязана быть effectively final. Такое требование исходит из следующих причин.

Локальная переменная хранится на стеке, а значит время ее жизни в отличие от долгоживущих элементов хипа ограничено скоупом и текущим потоком. Но экземпляр лямбды, захвативший эту переменную, мог бы быть передан наружу и использован для доступа к этой переменной из другого потока и после выхода из метода.

Эта проблема решается тем, что в лямбду копируется значение локальной переменной. Такая копия живет независимо, возможно дольше оригинала. Но это решение приведет к сложному поведению из-за возможности работы с неактуальным значением – копия и оригинал станут двумя разными переменными. Поэтому значение должно быть вечно актуально – неизменяемо.

Поля экземпляра менять можно, потому что захваченной переменной в этом случае выступает effectively final значение this.

Если локальную переменную всё же хочется изменить, решение очевидно – поместить её в кучу. Для этого нужно использовать любого рода обертку: одноэлементный массив, объект-atomic, специально созданный класс с этой переменной как полем.

Хак с оберткой решает проблему времени жизни и даёт коду скомпилироваться, но возвращает проблему сложности поведения. Если среда многопоточная, то вероятно порядок операций с этой переменной придется синхронизировать вручную.


Подписывайся на наш канал в Max 🟪
Please open Telegram to view this post
VIEW IN TELEGRAM
👍7🔥32
Please open Telegram to view this post
VIEW IN TELEGRAM
2🔥1
Какими коллекциями пользоваться в многопоточной среде?

Первый вариант – превратить в синхронизированную обычную коллекцию, вызвав соответствующий ее типу метод Collections.synchronized*(). Самый общий и самый примитивный способ, создает обертку с синхронизацией всех операций с помощью synchronized.

Если работа с коллекцией состоит в основном из чтения, лучшая в плане производительности альтернатива – CopyOnWriteArrayList, и содержащий его в реализации CopyOnWriteArraySet. Потокобезопасность достигается копированием внутреннего массива при любой модификации, оригинальный массив остается immutable. Program order достигается модификатором volatile на внутреннем массиве.

Третий вариант – использование Concurrent-коллекций:
🔘 Неблокирующие хэш-таблицы ConcurrentSkipListMap, ConcurrentHashMap и ConcurrentSkipListSet (хэш-таблица в основе реализации)
🔘 Неблокирующие очереди ConcurrentLinkedQueue и ConcurrentLinkedDeque
🔘 Большой набор различных блокирующих очередей

Подписывайся на наш канал в Max 🟪
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥8👍31
Скачал Java, написал код, запустил - работает. Можно выдохнуть? 😎

Спойлер: нет.
Потому что завтра нужно будет написать что-то свое, без туториала. И тут выяснится, что public static void main - это для тебя просто магический ритуал, а не код.

Копировать с экрана - не значит понимать.
Выучить 10 уроков на YouTube - не значит стать разработчиком.

❇️ Ребята из Merion Academy (того самого YouTube-канала про IT) на бесплатных вводных уроках по Java разбирают код построчно, чтобы ты не просто копировал, а понимал, что пишешь.

Что внутри:
✔️ Что такое Java и с чем ее едят
✔️ Как настроить среду без боли (чтобы все взлетело с первого раза)
✔️ Разбор синтаксиса построчно - никакой магии
✔️ Как написать свое первое REST API (да, сразу)

➡️ Запишись на бесплатные вводные уроки

Разберись, как Java работает на самом деле.
🤣5👍3🔥2
new String("Hello_42") == "Hello_" + 42?

Такие вопросы о сравнении строковых и числовых констант проверяют знания о понятии пулов литералов (literal pool). Не следует путать с пулом констант класса. Виртуальная машина переиспользует один и тот же объект для строкового литерала при загрузке класса, если такой уже выделен в куче. Вот почему "Hello" == "Hello" истинно, не смотря на то что String – ссылочный тип. Такая оптимизация возможна благодаря свойству неизменяемости (immutable) класса String, и называется интернирование строк.

Кроме самих литералов, интернирование применяется ко всем константным выражениям. К таким выражениям в этом примере относятся неявное приведение числа 42 к строке и конкатенация констант. Это делает истинным "Hello_42" == "Hello_" + 42.

Пул литералов не работает, когда явно используется оператор new. Это причина, по которой выражение new String("Hello_42") == "Hello_" + 42 ложно.


Подписывайся на наш канал в Max 🟪
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥6👍52
Please open Telegram to view this post
VIEW IN TELEGRAM
👍3🔥2🥴1
Что произойдёт, если пользователь с ролью ROLE_USER выполнит GET-запрос к/api/admin?
👍3🔥2
Что произойдёт, если пользователь с ролью ROLE_USER выполнит GET-запрос к/api/admin?
Anonymous Quiz
2%
Пользователь получит доступ, так как он аутентифицирован
5%
Запрос пройдёт, потому что hasAnyRole допускает USER
5%
Возникнет ошибка компиляции - @PreAuthorize не поддерживается для контроллеров
79%
Будет выброшено исключение AccessDeniedException, доступ запрещён
10%
Пользователь будет перенаправлен на /login
👍5🔥31