что такое состояние гонки в java

Что такое состояние гонки в java

Data Race / Состояние гонки

Data race или состояние гонки — это ошибка проектирования многопоточной системы, при которой работа программы зависит от порядка выполнения частей кода, которые не синхронизированы должным образом.

Обновление от 12.07.2021: Судя по всему, в статье произошла путаница терминов. Data Race — это не состояние гонки. Скорее race condition нужно переводить как состояние гонки, а data race — это немного другое. Статью, похоже, нужно переписать или поправить.

Состояние гонки зачастую сложно исправить, так как оно проявляется в случайные моменты времени и пропадает при попытке её локализовать. Data race возникает при условии:

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

Русские Блоги

Многопоточность JAVA (два), состояние гонки, взаимоблокировка и механизм синхронизации

4 Многопоточные проблемы безопасности и решения

В этой записи блога я перечислю проблемы безопасности (состояния гонки, взаимоблокировки и т. Д.), Которые могут возникнуть в процессе многопоточного программирования JAVA, и соответствующие решения, такие как Механизм синхронизации Подождите.

Что такое безопасность потоков? Проще говоря, Если ваш код всегда будет получать один и тот же результат при выполнении в нескольких потоках и в одном потоке, тогда ваш код является поточно-ориентированным.

4.1 Состояние гонки (условия гонки) и многопоточный механизм синхронизации

4.1.1 Понятие условий гонки

Thread-0:0
Thread-0:2
Thread-1:1
Thread-0:3
Thread-0:5
Thread-0:6
Thread-0:7
Thread-0:8
Thread-0:9
Thread-0:10
Thread-0:11
Thread-1:4
Thread-1:12
Thread-1:13
Thread-1:14
Thread-1:15
Thread-1:16
Thread-1:17
Thread-1:18
Thread-1:19
В этом примере оба потока получат доступ к индексу статической переменной. Время получения системного временного интервала является неопределенным, поэтому их доступ и изменение индекса всегда чередуются.

4.1.2 Многопоточная синхронизация

Есть три способа добиться синхронизации:

Метод синхронизации описание
Синхронизированное ключевое слово: за счет больших системных издержек, используйте его с осторожностью Когда поток вызывает фрагмент синхронизированного кода объекта, он должен сначала получить блокировку, затем выполнить соответствующий код и снять блокировку после завершения выполнения. Существует два варианта использования синхронизированного ключевого слова-1. синхронизированного метода. Безопасность 2. Если тело метода большое, следует использовать синхронизированный блок. Он может либо объявить любой сегмент кода как синхронизированный, либо указать заблокированный объект, что очень гибко.
wait() notify() Во время выполнения синхронизированного кода поток может вызвать метод wait () объекта, снять блокировку объекта, войти в состояние ожидания и может вызвать метод notify () или notifyAll () для уведомления других потоков, которые ожидают.
Блокировка (добавлена ​​в JDK5, класс реализации — ReentrantLock) Он предоставляет четыре метода: 1. lock () получает блокировку блокирующим способом и возвращает ее немедленно, если она была получена. Если другой поток удерживает блокировку, текущий поток ждет, пока не вернется после получения блокировки; 2.tryLock () Получите блокировку неблокирующим способом. Просто попробуйте получить блокировку, если получение выполнено успешно, верните true, в противном случае верните false; 3.tryLock (long timeout, TimeUnit unit) Если блокировка получена, верните true, в противном случае ожидайте данную единицу времени и, если она была получена в процессе ожидания Блокировка, возврат true и возвращение false при превышении времени 4. LockInterruptibly () Если блокировка получена, немедленно вернитесь; если блокировка не получена, текущий поток спит, пока блокировка не будет получена или текущий поток не прерван другим потоком.

4.1.3 synchronized

Структура синхронизированного блока:

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

Результаты этого прогона:
Thread-1:0
Thread-1:1
Thread-1:2
Thread-1:3
Thread-1:4
Thread-1:5
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9
Thread-0:10
Thread-0:11
Thread-0:12
Thread-0:13
Thread-0:14
Thread-0:15
Thread-0:16
Thread-0:17
Thread-0:18
Thread-0:19
Можно видеть, что после использования синхронизации потоки будут обращаться к статическим переменным в том порядке, то есть механизм синхронизации решает условие гонки путем «блокировки».

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

4.1.4 Lock vs synchronized

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

Вот пример использования Lock:

Результат выполнения программы:
interrupted.

4.2 тупик (тупик)

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

При каких обстоятельствах возникнет тупик? Одновременно Следующие условия

Состояние тупика описание
Не обделен Ресурсы, уже полученные процессом, не могут быть принудительно лишены перед использованием
Запрос и удержание Когда процесс заблокирован из-за запроса ресурсов, сохраните полученные ресурсы
Взаимоисключающий Ресурс может использоваться только одним процессом за раз
Цикл ожидания Несколько процессов формируют круговое отношение ресурса ожидания к хвосту

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

Здесь мы даем пример, чтобы проиллюстрировать тупик и избежать тупика (программа цитируетсяHow to avoid deadlock). Следующая программа вызовет взаимоблокировку, потому что, если поток 1 получает блокировку объекта String при выполнении method1 (), а поток 2 получает блокировку объекта Integer при выполнении method2 (), обе стороны будут входить в бесконечное взаимное Состояние ожидания, поскольку обе стороны хотят получить блокировку объекта, полученную другой стороной. к
9b5fdcddb84e20c8d18fcfff7923b2e3

Согласно сказанному выше, вы можете избежать тупиковых ситуаций, внеся следующие изменения в программу. Когда поток 1 получает блокировку объекта Integer, поток 2 будет ожидать, пока поток 1 не снимет блокировку, прежде чем выполнить, и наоборот. к
87ecb043517e56f2505f134757b34ff7

Состояние гонки

2*a769TWQ4NJdHJc7tpd tPw

Вновь приветствую вас в теме “Синхронизация в Java”! Надеюсь, что вы прочли мою предыдущую статью.

Давайте разберёмся, что же такое состояние гонки. Это состояние проявляется, когда нам нужно обратиться к данным параллельно. Хорошо, тогда что же значит параллельное обращение к данным? Проще говоря, это означает, что два разных потока могут считывать одну и ту же переменную, поле, или даже массив, определённые внутри класса Java. Давайте возьмём популярный шаблон проектирования “Singleton” и посмотрим, как в нём проявляется такое состояние гонки.

Два потока пытают с я выполнить этот блок кода. Представьте себе ситуацию, где поток 1 (T1) задерживается в блоке if, в это время в процесс включается T2 и в итоге завершает блок if созданием “статического экземпляра Singleton”, после чего опять запускается T1 и уничтожает только что созданный T2 экземпляр.

Как же этого избежать? В этом случае помогает синхронизация, которая не даёт выполнять блок кода более чем одному потоку одновременно.

Значит синхронизация решит проблему? Да, именно так. Теперь давайте посмотрим её в действии на примере образов. В них мы увидим, как ключевое слово synchronized защищает методы.

Для большей наглядности я подготовил серию рисунков. Образ человека будет представлять собой поток. Взгляните на то, как этот человек просит ключ и попадает с его помощью в метод, а затем, покидая этот метод, возвращает ключ обратно. Поэтому другой человек (поток 2) должен дождаться, чтобы также получить ключ. Достаточно простой принцип, не так ли?

По факту из сказанного следует, что нам нужен объект, который будет содержать ключ, делая подобную синхронизацию возможной. В случае, приведённом выше, мы поместили ключевое слово synchronized в public static method. А что же для преодоления блокировки в таком случае использует JVM? Объект Singleton.class. Т.е. схожим образом в случае синхронизации в нестатических методах JVM использует в качестве объекта синхронизации конкретный экземпляр, в котором Singleton.class находится.

Давайте используем для синхронизации явный объект

Мы можем использовать для выполнения синхронизации явный объект, как это показано в блоке кода ниже. Да, достаточно только самого класса объекта. Я думаю, что вы уже знаете почему. Вместо синхронизирования метода getName() мы можем использовать синхронизированный блок внутри этого метода и передать объект key в качестве параметра ключевого слова synchronized. Помните, что это всегда будет удачным решением.

Синхронизация более чем одного метода

Предположим, что у нас есть класс Student с двумя синхронизированными методами getName() и getMarks(). Объект блокировки, используемый JVM, находится в самом объекте Student. Когда конкретный поток захочет выполнить getName(), он возьмёт этот объект блокировки, тем самым лишая другой поток возможности выполнить этот же метод одновременно с ним. Поскольку мы не объявляли явный объект в синхронизации наших методов, будет использован тот же объект key. Итак, теперь становится понятно, что для независимого выполнения этих двух методов в одно и то же время нам нужно создать в классе Student два объекта блокировки и синхронизировать эти два блока кода из 2 блокировок (2 разных объектов).

Теперь предположим, что у нас есть два экземпляра класса Student: Student1 и Student2. Синхронизирование более одного метода заблокирует объекты двумя ключами.

0*WHY9Cnv6cSvCDk7k

Потоку, выполняющему getName(), объект Student1 не мешает выполнить getMarks() в объекте Student2.

0*Nb2bBq1JKUBngNak

И он не пересечётся с другим потоком, который будет затем выполнять тот же метод getName() в объекте Student2. Думаю, что приведённые рисунки достаточно наглядно это демонстрируют.

А что, если мы хотим помешать двум потокам выполнять метод getName() одновременно во всех экземплярах класса Student?

Теперь вы понимаете, для чего нам нужен объект блокировки, который привязан ни к одному из экземпляров класса Student, а к самому классу, не так ли? В таком случае это должно быть поле Static самого класса Student.

Теперь становится ясно, что поток, который выполняет Student1, удерживая ключ, не даст другим потокам выполнять методы в экземпляре Student2 класса Student.

0*2R3YPqmFo2Rv7q9W

ReEntrant Lock (блокировка с повторным входом)

Снова представим, что у нас есть два экземпляра класса Student с несколькими блоками synchronized.

Одни и те же ключи должны открывать одни и те же замки, согласны?

В этих двух экземплярах method1() и method3() защищены одной и той же красной блокировкой, и вам нужен именно красный ключ. То же самое касается method4() и method1(). Итак, поток, выполняющий method1 в объекте Student1, получает ключ, и в определённый момент он войдёт в синхронизированный method3 экземпляра Student2. Поскольку этот поток уже имеет правильный ключ, то он будет допущен к выполнению и другого метода.

Когда поток удерживает блокировку, он может войти в блок, синхронизированный с этой блокировкой.

Источник

Многопоточность в Java: суть, «плюсы» и частые ловушки

Проблемы, которые решает многопоточность в Java

Одновременно выполнять несколько действий.

В примере выше разные потоки (т.е. члены семьи) параллельно выполняли несколько действий: мыли посуду, ходили в магазин, складывали вещи.

Можно привести и более «программистский» пример. Представь, что у тебя есть программа с пользовательским интерфейсом. При нажатии кнопки «Продолжить» внутри программы должны произойти какие-то вычисления, а пользователь должен увидеть следующий экран интерфейса. Если эти действия осуществляются последовательно, после нажатия кнопки «Продолжить» программа просто зависнет. Пользователь будет видеть все тот же экран с кнопкой «Продолжить», пока все внутренние вычисления не будут выполнены, и программа не дойдет до части, где начнется отрисовка интерфейса.

Что ж, подождем пару минут!

1024

А еще мы можем переделать нашу программу, или, как говорят программисты, «распараллелить». Пусть нужные вычисления выполняются в одном потоке, а отрисовка интерфейса — в другом. У большинства компьютеров хватит на это ресурсов. В таком случае программа не будет «тупить», и пользователь будет спокойно переходить между экранами интерфейса не заботясь о том, что происходит внутри. Одно другому не мешает 🙂

Ускорить вычисления.

Тут все намного проще. Если наш процессор имеет несколько ядер, а большинство процессоров сейчас многоядерные, список наших задач могут параллельно решать несколько ядер. Очевидно, что если нам нужно решить 1000 задач и каждая из них решается за секунду, одно ядро справится со списком за 1000 секунд, два ядра — за 500 секунд, три — за 333 с небольшим секунды и так далее.

Источник

Разбор основных концепций параллелизма

Завтра у нас плавненько стартует практически юбилейный поток курс «Разработчик Java» — уже шестой по счёту начиная с апреля прошлого года. А это значит, что мы снова подобрали, перевели интереснейший материал, которым делимся с вами.

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

С момента своего создания Java поддерживает ключевые концепции параллелизма, такие как потоки и блокировки. Эта памятка поможет Java-разработчикам, работающим с многопоточными программами, понять основные концепции параллелизма и способы их применения.

Концепция Описание
Атомарная операция — это операция, которая выполняется полностью или не выполняется совсем, частичное выполнение невозможно.
Visibility (видимость) Условия, при которых один поток видит изменения, сделанные другим потоком

Таблица 1: Концепции параллелизма

6kiatzehfeziuhf24ee0usgien0

Состояние гонки (Race condition)

Гонка данных (Data race)

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

Модель памяти Java: отношение happens-before

Модель памяти Java определяется с точки зрения таких действий, как чтение/запись полей и синхронизация в мониторе. Действия упорядочены с помощью отношения happens-before (выполняется прежде), которое может быть использовано для объяснения того, когда поток видит результат действий другого потока, и что представляет собой правильно синхронизированная программа.

ОТНОШЕНИЯ HAPPENS-BEFORE ИМЕЮТ СЛЕДУЮЩИЕ СВОЙСТВА:

image loader
Изображение 1: Пример happens-before

Стандартные функции синхронизации

Ключевое слово synchronized

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

Ключевое слово synchronized можно также раскрыть на уровне методов.

ССЫЛКА, ИСПОЛЬЗУЕМАЯ КАК МОНИТОР
static ссылка на объект Class
non-static this-ссылка

Таблица 2: Мониторы, которые используются, когда весь метод синхронизирован

Блокировка реентерабельна (reentrant), поэтому, если поток уже содержит блокировку, он может успешно получить ее снова.

Уровень соперничества влияет на способ захвата монитора:

Описание
init Только что создан, пока никем не был захвачен.
biased Борьбы нет, и код, защищенный блокировкой, выполняется только одним потоком. Самый дешевый для захвата.
thin Монитор захватывается несколькими потоками без борьбы. Для блокировки используется сравнительно дешевый CAS.
fat Возникает борьба. JVM запрашивает мьютексы ОС и позволяет планировщику ОС обрабатывать парковки потоков и пробуждения.

Таблица 3: Состояния мониторов

volatile решает проблему видимости и делает изменение значения атомарным, потому что здесь есть отношение happens-before: запись в volatile-переменную происходит до любого последующего считывания volatile-переменной. Таким образом, оно гарантирует, что при последующем считывании поля будет видно значение, которое было задано самой последней записью.

Используя классы AtomicXXX, можно реализовать атомарную операцию check-then-act :

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

Убедитесь, что this-ссылка не испарилась во время создания.

Одним из самых замечательных свойств неизменяемых объектов является потокобезопасность, поэтому синхронизация для них не нужна. Требования к неизменному объекту:

Класс java.lang.Thread используется для представления приложения или потока JVM. Код всегда выполняется в контексте некоторого класса Thread (чтобы получить текущий поток вы можете использовать Thread#currentThread()).

Описание
NEW Не запускался.
Запущен и работает.
BLOCKED Ожидание на мониторе — он пытается получить блокировку и войти в критическую секцию.
WAITING Ожидание выполнения определенного действия другим потоком (notify/notifyAll, LockSupport#unpark).
То же, что и WAITING, но с таймаутом.
TERMINATED Остановлен

Таблица 4: Состояния потоков

Описание
start Запускает экземпляр класса Thread и выполняет метод run().
join Блокирует до окончания потока.
interrupt Прерывает поток. Если поток заблокирован в методе, который отвечает на прерывания, в другом потоке будет брошен InterruptedException, в противном случае будет установлен статус прерывания.
stop, suspend, resume, destroy Все эти методы устарели. Они выполняют опасные операции в зависимости от состояния рассматриваемого потока. Вместо них используйте Thread#interrupt() или флаг volatile, чтобы указать потоку, что он должен делать

Таблица 5: Thread coordination methods Методы координации потоков

Как обрабатывать InterruptedException?

Пример потенциального дэдлока:

Взаимная блокировка происходит, если в одно и то же время:

JVM способен обнаруживать взаимные блокировки мониторов и выводить информацию о них в дампах потоков.

Livelock и потоковое голодание

Livelock возникает, когда потоки тратят все свое время на переговоры о доступе к ресурсу или обнаруживают и избегают тупиковой ситуации так, что поток фактически не продвигается вперед. Голодание возникает, когда потоки сохраняют блокировку в течение длительных периодов, так что некоторые потоки «голодают» без прогресса.

Основным интерфейсом для пулов потоков является ExecutorService.java.util.concurrent также предоставляет статическую фабрику Executors, которая содержит фабричные методы для создания пула потоков с наиболее распространенными конфигурациями.

Метод Описание
newSingleThreadExecutor Возвращает ExecutorService только с одним потоком.
newFixedThreadPool Возвращает ExecutorService с фиксированным количеством потоков.
newCachedThreadPool Возвращает ExecutorService с пулом потоков различного размера.
Возвращает ScheduledExecutorService с одним потоком.
newScheduledThreadPool Возвращает ScheduledExecutorService с основным набором потоков.
newWorkStealingPool Возвращает крадущий задачи ExecutorService.

Таблица 6: Методы статической фабрики

Реализация Описание
ThreadPoolExecutor Реализация по умолчанию с изменяющим размер пулом потока, одной рабочей очереди и настраиваемой политикой для отклоненных задач (через RejectedExecutionHandler) и создания потоков (через ThreadFactory).
Расширение ThreadPoolExecutor, которое обеспечивает возможность планирования периодических задач.
ForkJoinPool Крадущий задачи пул: все потоки в пуле пытаются найти и запустить либо поставленные задачи, либо задачи, созданные другими активными задачами.

Таблица 7: Реализации пула потоков

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

Таблица 8: Функциональные интерфейсы задач

Future — это абстракция для асинхронного вычисления. Она представляет результат вычисления, который может быть доступен в какой-либо момент: либо вычисленное значение, либо исключение. Большинство методов ExecutorService используют Future как возвращаемый тип. Он предоставляет методы для изучения текущего состояния future или блокирует до тех пор, пока не будет доступен результат.

Пакет java.util.concurrent.locks также содержит интерфейс ReadWriteLock (и реализацию ReentrantReadWriteLock), который определяется парой блокировок для чтения и записи, обычно позволяя считывать одновременно нескольким читателям, но допуская только одного писателя.

CompletableFuture является абстракцией для произведения асинхронных вычислений. В отличие от простого Future, где единственная возможность получить результат — блокировать, рекомендуется регистрировать обратные вызовы для создания конвейера задач, которые должны выполняться, когда доступен результат или исключение. Либо во время создания (через CompletableFuture#supplyAsync/runAsync ), либо во время добавления обратных вызовов (методы семейства *async ) может быть указан исполнитель, где должно выполняться вычисление (если он не указан стандартным глобальным ForkJoinPool#commonPool ).

Учтите, что если CompletableFuture уже завершен, обратные вызовы, зарегистрированные с помощью не *async методов, будут выполняться в вызывающем потоке.

Реализация Описание
Предоставляет семантику копирования при записи, где каждая модификация структуры данных приводит к новой внутренней копии данных (поэтому запись очень дорогая, тогда как чтение дешевое). Итераторы в структуре данных всегда видят снепшот данных с момента создания итератора.

Таблица 9: Списки в java.util.concurrent

Описание
Обычно выступает в качестве сегментированной хэш-таблицы. Операции чтения, как правило, не блокируют и отражают результаты последней завершенной записи. Запись первого узла в пустой ящик выполняется просто CAS-ом (сравнить и установить), тогда как другим операциям записи требуются блокировки (первый узел сегмента используется как блокировка).
Обеспечивает параллельный доступ наряду функциональностью сортированного Map, подобной TreeMap. Границы производительности такие же как у TreeMap, хотя несколько потоков обычно могут читать и записывать из ассоциативного массива без конфликтов, если они не изменяют одну и ту же часть отображения.

Таблица 10: Ассоциативные массивы в java.util.concurrent

Описание
Подобно CopyOnWriteArrayList, он использует семантику copy-on-write для реализации интерфейса Set.
Подобно ConcurrentSkipListMap, но реализует интерфейс Set.

Таблица 11: Множества в java.util.concurrent

Другим подходом к созданию параллельного множества является обертка параллельного Map:

Источник

Читайте также:  Кидаться тортом во сне что
DACHARAI - самый большой ресурс для садовода
Adblock
detector