для чего нужны atomic типы данных java

Атомарные классы пакета util.concurrent

Пакет java.util.concurrent.atomic содержит девять классов для выполнения атомарных операций. Операция называется атомарной, если её можно безопасно выполнять при параллельных вычислениях в нескольких потоках, не используя при этом ни блокировок, ни синхронизацию synchronized. Прежде, чем перейти к рассмотрению атомарных классов, рассмотрим выполнение наипростейших операций инкремента и декремента целочисленных значений.

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

Описание атомарного класса AtomicLong

Рассмотрим принцип действия механизма оптимистической блокировки на примере атомарного класса AtomicLong, исходный код которого представлен ниже. В этом классе переменная value объявлена с модификатором volatile, т.е. её значение могут поменять разные потоки одновременно. Модификатор volatile гарантирует выполнение отношения happens-before, что ведет к тому, что измененное значение этой переменной увидят все потоки.

Метод compareAndSet реализует механизм оптимистической блокировки. Знакомые с набором команд процессоров специалисты знают, что ряд архитектур имеют инструкцию Compare-And-Swap (CAS), которая является реализацией этой самой операции. Таким образом, на уровне инструкций процессора имеется поддержка необходимой атомарной операции. На архитектурах, где инструкция не поддерживается, операции реализованы иными низкоуровневыми средствами.

Основная выгода от атомарных (CAS) операций появляется только при условии, когда переключать контекст процессора с потока на поток становится менее выгодно, чем немного покрутиться в цикле while, выполняя метод boolean compareAndSwap(oldValue, newValue). Если время, потраченное в этом цикле, превышает 1 квант потока, то, с точки зрения производительности, может быть невыгодно использовать атомарные переменные.

Список атомарных классов

Атомарные классы пакета java.util.concurrent.atomic можно разделить на 4 группы :

• AtomicBoolean
• AtomicInteger
• AtomicLong
• AtomicReference
Atomic-классы для boolean, integer, long и ссылок на объекты.
Классы этой группы содержат метод compareAndSet, принимающий 2 аргумента : предполагаемое текущее и новое значения. Метод устанавливает объекту новое значение, если текущее равно предполагаемому, и возвращает true. Если текущее значение изменилось, то метод вернет false и новое значение не будет установлено.
Кроме этого, классы имеют метод getAndSet, который безусловно устанавливает новое значение и возвращает старое.
Классы AtomicInteger и AtomicLong имеют также методы инкремента/декремента/добавления нового значения.
• AtomicIntegerArray
• AtomicLongArray
• AtomicReferenceArray
Atomic-классы для массивов integer, long и ссылок на объекты.
Элементы массивов могут быть изменены атомарно.
• AtomicIntegerFieldUpdater
• AtomicLongFieldUpdater
• AtomicReferenceFieldUpdater
Atomic-классы для обновления полей по их именам с использованием reflection.
Смещения полей для CAS операций определяется в конструкторе и кэшируются. Сильного падения производительности из-за reflection не наблюдается.
• AtomicStampedReference
• AtomicMarkableReference
Atomic-классы для реализации некоторых алгоритмов, (точнее сказать, уход от проблем при реализации алгоритмов).
Класс AtomicStampedReference получает в качестве параметров ссылку на объект и int значение.
Класс AtomicMarkableReference получает в качестве параметров ссылку на объект и битовый флаг (true/false).

Полная документация по атомарным классам на английском языке представлена на оффициальном сайте Oracle. Наиболее часто используемые классы (не трудно догадаться) сосредоточены в первой группе.

Производительность атомарных классов

Согласно множеству источников неблокирующие алгоритмы в большинстве случаев более масштабируемы и намного производительнее, чем блокировки. Это связано с тем, что операции CAS реализованы на уровне машинных инструкций, а блокировки тяжеловесны и используют приостановку и возобновление потоков, переключение контекста и т.д. Тем не менее, блокировки демонстрируют лучший результат только при очень «высокой конкуренции», что в реальной жизни встречается не так часто.

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

Пример неблокирующего генератора последовательности

Листинг класса SequenceGenerator для генерирования последовательности

Для работы в многопоточной среде без блокировок используем атомарную ссылку AtomicReference, которая обеспечит хранение целочисленного значения типа java.math.BigInteger. Метод next возвращает текущее значение; переменная next вычисляет следующее значение. Метод compareAndSet атомарного класса element обеспечивает сохранение нового значения, если текущее не изменилось. Таким образом, метод next возвращает текущее значение и увеличивает его в 2 раза.

Листинг последовательности Sequence

Для тестирования генератора последовательности SequenceGenerator используем класс Sequence, реализующий интерфейс Runnable. В качестве параметра конструктор класса получает идентификатор потока id, размер последовательности count и генератор последовательности sg. В методе run в цикле с незначительными задержками формируется последовательность чисел sequence. После завершения цикла значения последовательности «выводятся» в консоль методом printSequence.

Листинг примера SequenceGeneratorExample

В примере SequenceGeneratorExample сначала создается генератор последовательности SequenceGenerator. После этого в цикле формируется массив из десяти Sequence, которые в паралелльных потоках по три раза обращаются к генератору последовательсности.

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

При выполнении примера в консоль будет выведена следующая информация :

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

Скачать примеры

Рассмотренный на странице пример использования атомарного класса в виде проекта Eclipse можно скачать здесь (7.41 Кб).

Источник

Введение в атомарные переменные в Java

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

1. введение

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

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

2. Замки

Давайте взглянем на класс:

В случае однопоточной среды это работает идеально; однако, как только мы разрешаем писать более одного потока, мы начинаем получать противоречивые результаты.

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

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

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

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

3. Атомарные операции

Существует раздел исследований, посвященный созданию неблокирующих алгоритмов для параллельных сред. Эти алгоритмы используют низкоуровневые атомарные машинные инструкции, такие как compare-and-swap (CAS), для обеспечения целостности данных.

Типичная операция CAS работает с тремя операндами:

Операция CAS атомарно обновляет значение в M до B, но только в том случае, если существующее значение в M совпадает с A, в противном случае никаких действий не предпринимается.

В обоих случаях возвращается существующее значение в M. Это объединяет три этапа – получение значения, сравнение значения и обновление значения – в одну операцию на уровне машины.

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

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

4. Атомарные переменные в Java

Как вы можете видеть, мы повторяем операцию compareAndSet и снова при сбое, так как мы хотим гарантировать, что вызов метода increment всегда увеличивает значение на 1.

5. Заключение

В этом кратком руководстве мы описали альтернативный способ обработки параллелизма, при котором можно избежать недостатков, связанных с блокировкой. Мы также рассмотрели основные методы, предоставляемые классами атомарных переменных в Java.

Источник

Многопоточность в Java 8. Часть 1 (Атомарные типы)

В этой статье будет рассказано про важные улучшения в Concurrency API связанное с атомарными типами.
Для упрощение примеров кода будем использовать два вспомогательных метода sleep(seconds) и stop(executor) для остановки выполнения и освобождения ресурсов executor.

Atomic классы

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

Внутри атомарных типов (классов) интенсивно используется операция сравнение с обменом (CAS – compare-and-swap), она реализована на самом низком уровне как инструкция процессора. Эта атомарная инструкции, как правило, гораздо быстрее, чем синхронизация через блокировку. Так что атомарные типы лучше использовать вместо блокировки в том случае, когда нужно просто модифицировать одну переменную одновременно.

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

int processors = Runtime.getRuntime().availableProcessors(); AtomicLong num = new AtomicLong (0);
ExecutorService exec = Executors.newFixedThreadPool(processors);
for (int i = 0; i 100

При использовании AtomicLong в качестве замены для Long мы можем увеличивать число параллельно и потокобезопасно без синхронизации доступа к переменной. Метод incrementAndGet () является атомарной операцией, поэтому мы можем смело вызвать этот метод из нескольких потоков.

AtomicLong предоставляет большой набор методов для осуществления атомарных операций. К примеру, метод updateAndGet () принимает лямбда-функцию для того, чтобы выполнить произвольные арифметические операции над целыми числами:

Метод accumulateAndGet() принимает другой тип функции класса IntBinaryOperator. Мы используем этот метод, чтобы суммировать все числа от 0 до 100 одновременно в следующем примере:

stop(exec);
System.out.println(num.get()); // 4950

Также есть и другие полезные атомарные классы AtomicBoolean, AtomicInteger и AtomicReference.

Adder классы

Теперь давайте перейдем к новым типам, которые были добавлены в JDK 8, а именно классам сумматорам (Adder) и аккумуляторам (Accumulator), и как пример рассмотрим LongAdder.
Класс LongAdder, в качестве альтернативы AtomicLong, можно использовать для последовательного добавления значений.

LongAdder adder = new LongAdder();
for (int i = 0; i acc + c * c;
LongAccumulator accum = new LongAccumulator(func, 1);

for (int i = 0; i accum.accumulate(i)));
>

stop(exec);
System.out.println(accum.getThenReset()); // 328351

Мы создаем LongAccumulator с функцией acc + c*c и с начальным значением равным единице. При каждом вызове accumulate(i), текущий результат и значение i передаются как параметры в указанную выше функцию.

LongAccumulator так же, как и LongAdder сохраняют набор переменных внутри, чтобы уменьшить взаимодействие между потоками.
Помимо Long классов есть еще Double сумматоры и аккумуляторы – DoubleAdder и DoubleAccumulator.

Источник

Многопоточное программирование в Java 8. Часть третья. Атомарные переменные и конкурентные таблицы

Авторизуйтесь

Многопоточное программирование в Java 8. Часть третья. Атомарные переменные и конкурентные таблицы

AtomicInteger

Внутри атомарные классы очень активно используют сравнение с обменом (compare-and-swap, CAS), атомарную инструкцию, которую поддерживает большинство современных процессоров. Эти инструкции работают гораздо быстрее, чем синхронизация с помощью блокировок. Поэтому, если вам просто нужно изменять одну переменную с помощью нескольких потоков, лучше выбирать атомарные классы.

Как видите, использование AtomicInteger вместо обычного Integer позволило нам корректно увеличить число, распределив работу сразу по двум потокам. Мы можем не беспокоиться о безопасности, потому что incrementAndGet() является атомарной операцией.

Класс AtomicInteger поддерживает много разных атомарных операций. Метод updateAndGet() принимает в качестве аргумента лямбда-выражение и выполняет над числом заданные арифметические операции:

Среди других атомарных классов хочется упомянуть такие как AtomicBoolean, AtomicLong и AtomicReference.

LongAdder

Класс LongAdder может выступать в качестве альтернативы AtomicLong для последовательного сложения чисел.

LongAccumulator

ConcurrentMap

Интерфейс ConcurrentMap наследуется от обычного Map и предоставляет описание одной из самой полезной коллекции для конкурентного использования. Чтобы продемонстрировать новые методы интерфейса, мы будем использовать вот эту заготовку:

Если же вам нужно изменить таким же образом только один ключ, это позволяет сделать метод compute() :

ConcurrentHashMap

Это значение может быть специально изменено с помощью параметра JVM:

Для примеров ниже мы будем использовать всё ту же таблицу, что и выше (однако объявим её именем класса, а не интерфейса. чтобы нам были доступны все методы):

ForEach

Search

Или вот другой пример, который полагается только на значения:

Reduce

На этом всё. Надеюсь, мои статьи были вам полезны 🙂

Источник

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» ) ) ;
>

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *