JavaRush /Курсы /JSP & Servlets /Библиотека Java Concurrency

Библиотека Java Concurrency

JSP & Servlets
19 уровень , 0 лекция
Открыта

Многопоточность в Java

Java Virtual Machine поддерживает параллельные вычисления. Все вычисления могут быть выполнены в контексте одного или нескольких потоков. Мы легко можем настроить доступ к одному ресурсу или объекту для нескольких потоков, а также настроить поток на выполнения отдельного блока кода.

Любому разработчику необходимо синхронизировать работу с потоками при операциях чтения и записи для ресурсов, на которые выделены несколько потоков.

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

Объекты потока

Все начинается с главного (основного) потока, то есть минимально в твоей программе уже есть один выполняемый поток. Основной поток может создавать другие потоки с помощью Callable или Runnable. Создание отличается только возвращаемым результатом, Runnable не возвращает результата и не может выбросить проверяемое исключение. Поэтому у тебя получается хорошая возможность построить эффективную работу с файлами, но это очень опасно и нужно быть аккуратным.

Также есть возможность планировать выполнения потока на отдельном ядре центрального процессора. Система может легко перемещаться между потоками и выполнять определенный поток при правильных настройках: то есть выполняется сначала поток, который читает данные, как только у нас появились данные, далее мы передаем их потоку, который отвечает за валидацию, после этого передаем потоку для выполнения какой-то бизнес-логики и новым потоком записываем их обратно. В такой ситуации 4 потока поочередно обрабатывают данные и все будет работать быстрее, чем один поток. Каждый такой поток преобразуется в нативный поток ОС, а вот, каким способом его будут преобразовывать, зависит от реализации JVM.

Класс Thread служит для создания потоков и работы с ними. В нем есть стандартные механизмы управления, так и абстрактные, например, классы и коллекции из java.util.concurrent.

Синхронизация потоков в Java

Коммуникация обеспечивается за счет разделения доступа к объектам. Это весьма эффективно, но в то же время очень легко допустить ошибку при работе. Ошибки бывают двух случаев: thread interference — когда другой поток вмешивается в твой поток, и memory consistency errors — консистентности памяти. Для решения и предотвращения этих ошибок у нас есть разные методы синхронизации.

Синхронизацией потоков в Java занимаются мониторы, — это высокоуровневый механизм, позволяющий единовременно только одному потоку выполнять блок кода, защищённый этим же монитором. Поведение мониторов рассмотрено в терминах блокировок; один монитор — одна блокировка.

Синхронизация имеет несколько важных моментов, на которых нужно обратить внимание. Первый момент — это взаимное исключение (mutual exclusion) — только один поток может владеть монитором, таким образом, синхронизация на мониторе подразумевает, что как только один поток входит в synchronized-блок, защищённый монитором, никакой другой поток не может войти в блок, защищенный этим монитором, пока первый поток не выйдет из synchronized-блока. То есть несколько потоков не могут обратится в один блок synchronized одновременно.

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

Когда новый поток захватывает монитор, мы получаем доступ и возможность к исполнению этого блока кода, и в этот момент времени переменные будут загружены из основной памяти. Тогда мы сможем увидеть все записи, сделанные видимым предыдущим освобождением (release) монитора.

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

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

Volatile поля в Java

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

Запись работает как освобождение монитора, а чтение — как захват монитора. Доступ осуществляется в отношении по типу “выполняется прежде”. Если разобраться, то все, что будет видно для потока A, когда он обращался к volatile переменной, — это переменная для потока B. То есть вы гарантированно не потеряете ваши изменения из других потоков.

Volatile-переменные атомарны, то есть при чтении такой переменной используется такой же эффект, как и при получении блокировки — данные в памяти объявляются недействительными или некорректными и значение volatile переменной снова читается из памяти. При записи используется эффект для памяти, как и при освобождении блокировки — volatile-поле записывается в память.

Java Concurrent

Если ты хочешь сделать суперэффективное и многопоточное приложение, необходимо использовать классы из библиотеки JavaConcurrent, которые находятся в пакете java.util.concurrent.

Библиотека очень объемная и имеет разный функционал, поэтому давайте разберем, что есть внутри и поделим на некоторые модули:

Java Concurrent

Concurrent Collections — набор коллекций для работы в многопоточной среде. Вместо базового враппера Collections.synchronizedList с блокированием доступа ко всей коллекции используются блокировки по сегментам данных или используются wait-free алгоритмы для параллельного чтения данных.

Queues — неблокирующие и блокирующие очереди для работы в многопоточной среде. Неблокирующие очереди сосредоточены на скорости и работе без блокирования потоков. Блокирующие очереди подходят для работы, когда нужно “притормозить” потоки Producer или Consumer. Например, в той ситуации, когда не выполнены какие-то из условий, очередь пуста или переполнена, или же нет свободного Consumer'a.

Synchronizers — вспомогательные утилиты для синхронизации потоков. Представляют собой мощное оружие в “параллельных” вычислениях.

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

Locks — много гибких механизмов синхронизации потоков по сравнению с базовыми synchronized, wait, notify, notifyAll.

Atomics — классы, которые могут поддерживать атомарные операции над примитивами и ссылками.

Комментарии (8)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Мая Уровень 82
11 сентября 2025
Как это все перевести на русский? "Volatile-переменные атомарны, то есть при чтении такой переменной используется такой же эффект, как и при получении блокировки — данные в памяти объявляются недействительными или некорректными и значение volatile переменной снова читается из памяти. При записи используется эффект для памяти, как и при освобождении блокировки — volatile-поле записывается в память."
1 декабря 2024
Volatile-переменные атомарны Разве это верно? Ведь ничто не мешает прочитать переменную вторым потоком, пока эта-же переменная в участвует в первом потоке?
Павел Уровень 19 Expert
25 января 2024
Дочитал до фразы "Ошибки бывают двух случаев:...". Дальше как-то интерес пропал с лекцией знакомится. Хорошо, еще что смогли перевести как "двух случаев", а не "двух случек. Случка первая..."
Владислав Уровень 82 Expert
23 октября 2023
Синхронизация — это процесс, позволяющий выполнять в программе синхронно параллельные потоки. Несколько потоков могут мешать друг другу при обращении к одним и тем же объектам приложения. Для решения этой проблемы используется мьютекс, он же монитор, имеющий два состояния — объект занят и объект свободен. Монитор (мьютекс) — это высокоуровневый механизм взаимодействия и синхронизации процессов, обеспечивающий доступ к неразделяемым ресурсам. Мьютекс встроен в класс Object и, следовательно, имеется у каждого объекта. Синхронизация в Java реализуется использованием зарезервированного слова synchronized. Можно использовать synchronized в классах, определяя синхронизированные методы или блоки. Но нельзя использовать synchronized в переменных или атрибутах в определении класса. Когда какой-либо поток начинает использовать общий для всех потоков объект, то он проверяет мьютекс этого объекта. Если мьютекс свободен, то поток блокирует его, помечая как занятый, и приступает к использованию данного ресурса. После завершения работы, поток разблокирует мьютекс (помечает свободным). Если же поток обнаруживает, что объект заблокирован, то он «засыпает» в ожидании освобождения мьютекса. При освобождении мьютекса ожидающий поток тут же заблокирует его и приступит к работе. А как быть, если несколько потоков ожидают освобождения мьютекса? Кто первый встал, того и тапки … ? Эту проблему легко разрешит java.util.concurrent.Semaphore (см. ниже). Таким образом, при обычной синхронизации потоков используют оператор synchronized для ограничения (блокирования) доступа к определенному методу, блоку кода или объекту без каких-либо условий. Пакет java.util.concurrent содержит пять объектов синхронизации, позволяющих накладывать определенные условия для синхронизации потоков. https://java-online.ru/concurrent.xhtml#collections
Anonymous #3322801 Уровень 2 Expert
20 сентября 2023
Господи, да зачем так усложнять то объяснение volatile. Ключевое слово volatile в Java гарантирует, что значение переменной будет сразу же записано в основную память, а не в кэш процессора, и любые другие потоки будут видеть это обновленное значение.
Anonymous #3322801 Уровень 2 Expert
15 января 2024
Если лучше разбираетесь и много знаете , то поделись здесь с людьми объяснениями, ссылками и т.п., а если чисто поумничать зашли, то кыш с моих комментариев.
Надежда Уровень 104 Expert
3 августа 2023
Нормальное объяснение работы блокирующих очередей. В отличие от обычных очередей, блокирующие очереди предоставляют дополнительные механизмы синхронизации, которые позволяют приостановить выполнение потоков, если возникают определенные условия. Например, блокирующая очередь может быть полезна в ситуациях, когда поток-производитель (Producer) хочет поместить элемент в очередь, но очередь уже полная. Вместо того чтобы просто отказать в добавлении элемента, блокирующая очередь приостановит выполнение потока-производителя до тех пор, пока место не освободится в очереди. Аналогично, если поток-потребитель (Consumer) хочет извлечь элемент из пустой очереди, блокирующая очередь приостановит его до появления новых элементов. Таким образом, блокирующие очереди предоставляют удобный механизм для синхронизации работы потоков, позволяя им взаимодействовать и передавать данные друг другу без необходимости использовать явные механизмы синхронизации, такие как блокировки или семафоры. Они обеспечивают гладкую и эффективную обработку данных в многопоточных средах и позволяют "притормозить" потоки, когда это необходимо, чтобы избежать нежелательных условий или состояний.
Anton Rantsev Уровень 1 Expert
11 января 2023
интересная лекция