Volatile kotlin что это
Разбор основных концепций параллелизма
Завтра у нас плавненько стартует практически юбилейный поток курс «Разработчик Java» — уже шестой по счёту начиная с апреля прошлого года. А это значит, что мы снова подобрали, перевели интереснейший материал, которым делимся с вами.
Эта памятка поможет Java-разработчикам, работающим с многопоточными программами, понять основные концепции параллелизма и способы их применения. Вы ознакомьтесь с ключевыми аспектами языка Java со ссылками на стандартную библиотеку.
С момента своего создания Java поддерживает ключевые концепции параллелизма, такие как потоки и блокировки. Эта памятка поможет Java-разработчикам, работающим с многопоточными программами, понять основные концепции параллелизма и способы их применения.
Концепция | Описание |
---|---|
Атомарная операция — это операция, которая выполняется полностью или не выполняется совсем, частичное выполнение невозможно. | |
Visibility (видимость) | Условия, при которых один поток видит изменения, сделанные другим потоком |
Таблица 1: Концепции параллелизма
Состояние гонки (Race condition)
Гонка данных (Data race)
Гонка данных возникает, когда два или более потока пытаются получить доступ к одной и той же не финальной переменной без синхронизации. Отсутствие синхронизации может привести к внесению изменений, которые не будут видны другим потокам, из-за этого возможно чтение устаревших данных, что, в свою очередь, приводит к бесконечным циклам, поврежденным структурам данных или неточным вычислениям. Этот код может привести к бесконечному циклу, потому что считывающий поток может так и не заметить изменения, внесенные перезаписывающими потоками:
Модель памяти Java: отношение happens-before
Модель памяти Java определяется с точки зрения таких действий, как чтение/запись полей и синхронизация в мониторе. Действия упорядочены с помощью отношения happens-before (выполняется прежде), которое может быть использовано для объяснения того, когда поток видит результат действий другого потока, и что представляет собой правильно синхронизированная программа.
ОТНОШЕНИЯ HAPPENS-BEFORE ИМЕЮТ СЛЕДУЮЩИЕ СВОЙСТВА:
Изображение 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:
Volatile properties in Kotlin?
How does one mark a var in Kotlin volatile?
volatile public var tmpEndedAt: Long? = null
Is giving me the error: «unresolved reference: volatile».
3 Answers 3
I decided to give Kotlin a shot by just using the «convert java to kotlin» function in IntelliJ. Apparently that set things up wrong.
I tried doing the same thing, but after applying the Kotlin Gradle plugin and placing the file in src/kotlin and it all worked. Thanks for the help anyway guys.
According to the Kotlin documentation Kotlin-@Volatile
Marks the JVM backing field of the annotated property as volatile, meaning that writes to this field are immediately made visible to other threads.
So, in Kotlin you can mark the property as volatile with @Volatile annotation.
In other words, When you apply volatile to a field of a class, It instructs the CPU to always read it from the RAM and not from the CPU cache. It also prevents instructions reordering; it acts as a memory barrier.
check here for more information 🙂
Not the answer you’re looking for? Browse other questions tagged kotlin or ask your own question.
Related
Hot Network Questions
Subscribe to RSS
To subscribe to this RSS feed, copy and paste this URL into your RSS reader.
site design / logo © 2021 Stack Exchange Inc; user contributions licensed under cc by-sa. rev 2021.12.10.40971
By clicking “Accept all cookies”, you agree Stack Exchange can store cookies on your device and disclose information in accordance with our Cookie Policy.
5 вещей, которых вы не знали о многопоточности
Хоть от многопоточности и библиотек, которые её поддерживают, отказываются немногие Java-программисты, но тех, кто нашёл время изучить вопрос в глубину ещё меньше. Вместо этого мы узнаём о потоках только столько, сколько нам требуется для конкретной задачи, добавляя новые приёмы в свой инструментарий лишь тогда, когда это необходимо. Так можно создавать и запускать достойные приложения, но можно делать и лучше. Понимание особенностей компилятора и виртуальной машины Java поможет вам писать более эффективный, производительный код.
В этом выпуске серии «5 вещей …», я представлю некоторые из тонких аспектов многопоточного программирования, в том числе synchronized-методы, volatile переменные и атомарные классы. Речь пойдет в особенности о том, как некоторые из этих конструкций взаимодействуют с JVM и Java-компилятором, и как различные взаимодействия могут повлиять на производительность приложений.
Примечание переводчика: я как раз из тех людей, которые не знали этих пяти вещей о многопоточном программировании, поэтому посчитала, что эта статья стоит того, чтобы её обнародовать здесь, но и поэтому же могла допустить некоторые ошибки в переводе, так что поправки приветствуются с энтузиазмом.
Примечание переводчика2: в комментариях знающие люди делятся ссылками и информацией по теме, не менее интересными, чем содержание статьи)
1. Synchronized-метод или synchronized-блок?
Вы, возможно, уже задумывались о том, объявлять ли синхронизированным весь метод или только ту его часть, которую необходимо обезопасить. В таких ситуациях, полезно знать, что когда компилятор Java преобразует исходный код в байт-код, он работает с synchronized-методами и synchronized-блоками очень по-разному.
Когда JVM выполняет synchronized-метод, выполняющийся поток определяет, что в method_info этого метода проставлен флаг ACC_SYNCHRONIZED. Тогда он автоматически устанавливает блокировку на объект, вызывает метод и снимает блокировку. Если вылетает исключение, поток автоматически снимает блокировку.
С другой стороны, synchronized-блок обходит встроенную в JVM поддержку запросов блокировок объекта и обработку исключений, так что это необходимо описывать явно в байт-коде. Если вы посмотрите на байт-код для блока, увидите в нём кучу дополнительных операций в сравнении с методом. Листинг 1 показывает вызов и того, и другого.
Листинг 1. Два подхода к синхронизации.
package com.geekcap ;
public class SynchronizationExample <
private int i ;
public synchronized int synchronizedMethodGet ( ) <
return i ;
>
public int synchronizedBlockGet ( ) <
synchronized ( this ) <
return i ;
>
>
>
Метод synchronizedMethodGet() method генерирует следующий байт-код:
А вот байт-код для метода synchronizedBlockGet():
Создание synchronized-блока выдало 16 строк байт-кода, тогда как synchronized-метода – только 5.
2. «Внутрипоточные» (ThreadLocal) переменные.
Если вы хотите сохранить один экземпляр переменной для всех экземпляров класса, вы используете статические переменные класса. Если вы хотите сохранить экземпляр переменной для каждого потока, используйте внутрипоточные (ThreadLocal) переменные. ThreadLocal переменные отличаются от обычных переменных тем, что у каждого потока свой собственный, индивидуально инициализируемый экземпляр переменной, доступ к которой он получает через методы get() или set().
Предположим, вы разрабатываете многопоточный трассировщик кода, чьей целью является однозначное определение пути каждого потока через ваш код. Проблема в том, что вам необходимо скоординировать несколько методов в нескольких классах через несколько потоков. Без ThreadLocal это было бы трудноразрешимо. Когда поток начинал бы выполняться, было бы необходимо сгенерировать уникальный маркер для идентификации его трассировщиком, а потом передавать этот маркер каждому методу при трассировке.
С ThreadLocal это проще. Поток инициализирует ThreadLocal переменную в начале выполнения, а затем обращается к нему из каждого метода в каждом классе, и переменная при этом будет хранить трассировочную информацию только для исполняемого в данный момент времени потока. Когда его выполнение завершится, поток может передать свою индивидуальную запись о трассировке объекту управления, ответственному за поддержание всех записей.
Использование ThreadLocal имеет смысл, когда вам необходимо хранить экземпляры переменной для каждого потока.
3. Volatile переменные.
По моим оценкам, лишь половина всех разработчиков Java знает, что в Java есть ключевое слово volatile. Из них лишь около 10 процентов знают, что оно значит, и еще меньше знают, как эффективно его использовать. Короче говоря, определение переменной с ключевым словом volatile(«изменчивый») означает, что значение переменной будет изменяться разными потоками. Чтобы полностью понять, что значит volatile, во-первых, нужно понять, как потоки оперируют с обычными, не-volatile, переменными.
В целях повышения эффективности работы, спецификации языка Java позволяет JRE сохранять локальную копию переменной в каждом потоке, который ссылается на нее. Можно считать эти «внутрипоточные» копии переменных похожими на кэш, помогающий избежать проверки главной памяти каждый раз, когда требуется доступ к значению переменной.
Но представьте, что произойдёт в следующем случае: запустятся два потока, и первый прочитает переменную А как 5, тогда как второй – как 10. Если переменная А изменились от 5 до 10, то первый поток не будет знать об изменении, так что будет иметь неправильное значение А. Однако если переменная А будет помечена как volatile, то то в любое время, когда поток обращается к её значению, он будет получать копию А и считывать её текущее значение.
Если переменные в вашем приложении не меняются, то внутрипоточный кэш имеет смысл. В противном случае, очень полезно знать, что может сделать для вас ключевое слово volatile.
4. Volatile против synchronized.
int temp = 0 ;
synchronize ( myVolatileVar ) <
temp = myVolatileVar ;
>
synchronize ( myVolatileVar ) <
myVolatileVar = temp ;
>
Другими словами, если volatile переменная обновляется неявно, то есть значение читается, измененяется, а затем присваивается как новое, результат будет не-потокобезопасным между двумя синхронными операциями. Вы можете выбирать, следует ли использовать синхронизацию или рассчитывать на поддержку JRE автоматической синхронизации volatile переменных. Наилучший подход зависит от вашего случая: если присвоенное значение volatile переменной зависит от её текущего значения (например, во время операции инкремента), то нужно использовать синхронизацию, если вы хотите, чтобы операция была потокобезопасной.
5. Обновления атомарных полей.
Когда вам требуется примитивный тип, выполняющий операции инкремента и декремента, гораздо лучше выбрать его среди новых атомарных классов в пакете java.util.concurrent.atomic, чем писать synchronized блок самому. Атомарные классы гарантируют, что определённые операции будут выполняться потокобезопасно, например операции инкремента и декремента, обновления и добавления(add) значения. Список атомных классов включает AtomicInteger, AtomicBoolean, AtomicLong, AtomicIntegerArray, и так далее.
Своеобразным вызовом программисту в использовании атомарных классов является то, что все операции класса, включая get, set и семейство операций get-set тоже атомарные. Это значит, что операции чтения и записи, которые не изменяют значения атомарной переменной, синхронизированы, а не только важные операции чтения-обновления-записи. Если вы хотите более детального контроля над развертыванием синхронизированного кода, то обходной путь заключается в использовании атомарного апдейтера поля.
Использование атомарного апдейтера.
Атомарные апдейтеры типа AtomicIntegerFieldUpdater, AtomicLongFieldUpdater, и AtomicReferenceFieldUpdater по существу оболочки применяющиеся к volatile полям. Внутри, библиотеки классов Java используют их. Хотя они не часто используются в коде приложений, но у вас нет причин не начать облегчать свою жизнь с их помощью.
Листинг 2 демонстрирует пример класса, который использует атомарные обновления для изменения книги, которую кто-то читает:
Листинг 2. Класс Book.
public class Book
<
private String name ;
public String getName ( )
<
return name ;
>
Класс Book – просто POJO (plain old Java object – незамысловатый старый Java объект), у которого есть только одно поле: name.
Листинг 3. Класс MyObject.
/**
*
* @author shaines
*/
public class MyObject
<
private volatile Book whatImReading ;
public Book getWhatImReading ( )
<
return whatImReading ;
>
Класс MyObject в листинге 3 представляет, как и можно было ожидать, get и set методы, но метод set делает кое-что иное. Вместо того, чтобы просто предоставить свою внутреннюю ссылку на указанную книгу (что было бы выполнено закомментированным кодом в листинге 3), он использует AtomicReferenceFieldUpdater.
AtomicReferenceFieldUpdater
Javadoc определяет AtomicReferenceFieldUpdater так:
A reflection-based utility that enables atomic updates to designated volatile reference fields of designated classes. This class is designed for use in atomic data structures in which several reference fields of the same node are independently subject to atomic updates.
(Основанная на отражении утилита, которая разрешает атомарные обновления назначенным volatile ссылочным полям назначенных классов. Этот класс предназначен для использования в атомарных структурах данных, в которых несколько ссылочных полей одной и той же записи являются независимыми субъектами для атомарных обновлений)убейте меня, я не знаю, как это нормально перевести
В листинге 3 AtomicReferenceFieldUpdater создан через вызов метода newUpdater, который принимает три параметра.
• класс объекта, содержащего поле (в данном случае, MyObject)
• класс объекта, который будет обновляться атомарно (в данном случае, Book)
• имя поля для атомарного обновления
Значимым здесь является то, что метод getWhatImReading выполняется без синхронизации любого рода, в то время как setWhatImReading выполняется как атомарная операция.
В листинге 4 показано, как использовать setWhatImReading () и доказывается, что переменная изменяется правильно:
Листинг 4. Тест-кейс атомарного апдейтера.
import org.junit.Assert ;
import org.junit.Before ;
import org.junit.Test ;
public class AtomicExampleTest
<
private MyObject obj ;
@Before
public void setUp ( )
<
obj = new MyObject ( ) ;
obj. setWhatImReading ( new Book ( «Java 2 From Scratch» ) ) ;
>