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

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

Открыта

Многопоточность в 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 (для long и double это важно) или все обращения к нему защищены одной и той же блокировкой. Если синхронизации нет, возможны эффекты переупорядочивания (reordering): в некорректно синхронизированной программе один поток может увидеть изменения, сделанные другим потоком, в неожиданном порядке.

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

Volatile поля в Java

Ключевое слово volatile решает задачу видимости. Если поток записал значение в volatile-поле, любой другой поток, который потом прочитает это поле, увидит новое значение, а не устаревшую копию.

По эффекту на память запись в volatile-поле похожа на освобождение монитора, а чтение — на его захват. Всё, что поток A записал до записи в volatile-поле, гарантированно увидит поток B, если он прочитал из этого поля записанное значение. Это отношение называется happens-before («выполняется прежде»).

Отдельное чтение или запись volatile-поля атомарны, в том числе для long и double. А вот составные операции, например count++ (прочитать, увеличить, записать), — нет: между шагами может вклиниться другой поток, и часть увеличений потеряется. Взаимного исключения volatile не даёт. Чтобы составная операция выполнялась атомарно, нужны synchronized, Lock или классы AtomicInteger, AtomicLong и т.п.

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 — классы, которые могут поддерживать атомарные операции над примитивами и ссылками.

Комментарии (10)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Мая
Уровень 82
11 сентября 2025, 17:17
Как это все перевести на русский? "Volatile-переменные атомарны, то есть при чтении такой переменной используется такой же эффект, как и при получении блокировки — данные в памяти объявляются недействительными или некорректными и значение volatile переменной снова читается из памяти. При записи используется эффект для памяти, как и при освобождении блокировки — volatile-поле записывается в память."
1 декабря 2024, 03:51
Volatile-переменные атомарны Разве это верно? Ведь ничто не мешает прочитать переменную вторым потоком, пока эта-же переменная в участвует в первом потоке?
Dmytrii
Уровень 70
12 июля, 14:35
атомарны только операции чтения и записи volatile защищает не переменную, а ее ВИДИМОСТЬ. Если кто-то изменил переменную, остальные потоки быстро и гарантированно увидят это изменение
Павел
Уровень 19
Expert
25 января 2024, 14:07
Дочитал до фразы "Ошибки бывают двух случаев:...". Дальше как-то интерес пропал с лекцией знакомится. Хорошо, еще что смогли перевести как "двух случаев", а не "двух случек. Случка первая..."
Владислав
Уровень 82
Expert
23 октября 2023, 10:46
Синхронизация — это процесс, позволяющий выполнять в программе синхронно параллельные потоки. Несколько потоков могут мешать друг другу при обращении к одним и тем же объектам приложения. Для решения этой проблемы используется мьютекс, он же монитор, имеющий два состояния — объект занят и объект свободен. Монитор (мьютекс) — это высокоуровневый механизм взаимодействия и синхронизации процессов, обеспечивающий доступ к неразделяемым ресурсам. Мьютекс встроен в класс Object и, следовательно, имеется у каждого объекта. Синхронизация в Java реализуется использованием зарезервированного слова synchronized. Можно использовать synchronized в классах, определяя синхронизированные методы или блоки. Но нельзя использовать synchronized в переменных или атрибутах в определении класса. Когда какой-либо поток начинает использовать общий для всех потоков объект, то он проверяет мьютекс этого объекта. Если мьютекс свободен, то поток блокирует его, помечая как занятый, и приступает к использованию данного ресурса. После завершения работы, поток разблокирует мьютекс (помечает свободным). Если же поток обнаруживает, что объект заблокирован, то он «засыпает» в ожидании освобождения мьютекса. При освобождении мьютекса ожидающий поток тут же заблокирует его и приступит к работе. А как быть, если несколько потоков ожидают освобождения мьютекса? Кто первый встал, того и тапки … ? Эту проблему легко разрешит java.util.concurrent.Semaphore (см. ниже). Таким образом, при обычной синхронизации потоков используют оператор synchronized для ограничения (блокирования) доступа к определенному методу, блоку кода или объекту без каких-либо условий. Пакет java.util.concurrent содержит пять объектов синхронизации, позволяющих накладывать определенные условия для синхронизации потоков. https://java-online.ru/concurrent.xhtml#collections
Anonymous #3322801
Уровень 1
Expert
20 сентября 2023, 10:00
Господи, да зачем так усложнять то объяснение volatile. Ключевое слово volatile в Java гарантирует, что значение переменной будет сразу же записано в основную память, а не в кэш процессора, и любые другие потоки будут видеть это обновленное значение.
Anonymous #3322801
Уровень 1
Expert
15 января 2024, 13:46
Если лучше разбираетесь и много знаете , то поделись здесь с людьми объяснениями, ссылками и т.п., а если чисто поумничать зашли, то кыш с моих комментариев.
Underdante
Уровень 74
14 сентября, 07:17
не всегда , Вполне возможно когда 2 потока ОДНОВРЕМЕННО прочитали данные из рам памяти, и потом пытаются присвоить волатаил переменной новое значение
Надежда
Уровень 104
Expert
3 августа 2023, 11:28
Нормальное объяснение работы блокирующих очередей. В отличие от обычных очередей, блокирующие очереди предоставляют дополнительные механизмы синхронизации, которые позволяют приостановить выполнение потоков, если возникают определенные условия. Например, блокирующая очередь может быть полезна в ситуациях, когда поток-производитель (Producer) хочет поместить элемент в очередь, но очередь уже полная. Вместо того чтобы просто отказать в добавлении элемента, блокирующая очередь приостановит выполнение потока-производителя до тех пор, пока место не освободится в очереди. Аналогично, если поток-потребитель (Consumer) хочет извлечь элемент из пустой очереди, блокирующая очередь приостановит его до появления новых элементов. Таким образом, блокирующие очереди предоставляют удобный механизм для синхронизации работы потоков, позволяя им взаимодействовать и передавать данные друг другу без необходимости использовать явные механизмы синхронизации, такие как блокировки или семафоры. Они обеспечивают гладкую и эффективную обработку данных в многопоточных средах и позволяют "притормозить" потоки, когда это необходимо, чтобы избежать нежелательных условий или состояний.
Anton Rantsev Java Developer Expert
11 января 2023, 19:35
интересная лекция