Atomiclong java что это
Перейти к содержимому

Atomiclong java что это

  • автор:

Atomiclong java что это

A long значение, которое может быть обновлено атомарно. См. java.util.concurrent.atomic спецификация пакета для описания свойств атомарных переменных. AtomicLong используется в приложениях, таких как атомарно постепенно увеличенные порядковые номера, и не может использоваться в качестве замены для a Long . Однако, этот класс действительно расширяется Number предоставить универсальный доступ инструментами и утилитами, которые имеют дело с в цифровой форме на основе классами.

Сводка конструктора

Создает новый AtomicLong с начальным значением 0 .
Создает новый AtomicLong с данным начальным значением.

Сводка метода

Методы

Модификатор и Тип Метод и Описание
long addAndGet(long delta)

Атомарно добавляет данное значение к текущей стоимости.

Атомарно устанавливает значение в данное обновленное значение если текущая стоимость == математическое ожидание.

Атомарно декременты одним текущая стоимость.
Возвращает значение конкретного количества как a double .
Возвращает значение конкретного количества как a float .
Получает текущую стоимость.
Атомарно добавляет данное значение к текущей стоимости.
Атомарно декременты одним текущая стоимость.
Атомарно инкременты одним текущая стоимость.
Атомарно наборы к данному значению и возвратам старое значение.
Атомарно инкременты одним текущая стоимость.
Возвращает значение конкретного количества как int .
В конечном счете наборы к данному значению.
Возвращает значение конкретного количества как a long .
Наборы к данному значению.
Возвращает Строковое представление текущей стоимости.

Атомарно устанавливает значение в данное обновленное значение если текущая стоимость == математическое ожидание.

Методы java.lang унаследованный от класса. Число

Методы java.lang унаследованный от класса. Объект

Деталь конструктора

AtomicLong
public AtomicLong(long initialValue)

Создает новый AtomicLong с данным начальным значением.

AtomicLong
public AtomicLong()

Создает новый AtomicLong с начальным значением 0 .

Деталь метода

добраться
public final long get()

Получает текущую стоимость.

набор
public final void set(long newValue)

Наборы к данному значению.

lazySet
public final void lazySet(long newValue)

В конечном счете наборы к данному значению.

getAndSet
public final long getAndSet(long newValue)

Атомарно наборы к данному значению и возвратам старое значение.

compareAndSet
public final boolean compareAndSet(long expect, long update)

Атомарно устанавливает значение в данное обновленное значение если текущая стоимость == математическое ожидание.

weakCompareAndSet
public final boolean weakCompareAndSet(long expect, long update)

Атомарно устанавливает значение в данное обновленное значение если текущая стоимость == математическое ожидание. Может перестать работать побочно и не обеспечивает гарантии упорядочивания, так только редко соответствующая альтернатива compareAndSet .

getAndIncrement
public final long getAndIncrement()

Атомарно инкременты одним текущая стоимость.

getAndDecrement
public final long getAndDecrement()

Атомарно декременты одним текущая стоимость.

getAndAdd
public final long getAndAdd(long delta)

Атомарно добавляет данное значение к текущей стоимости.

incrementAndGet
public final long incrementAndGet()

Атомарно инкременты одним текущая стоимость.

decrementAndGet
public final long decrementAndGet()

Атомарно декременты одним текущая стоимость.

addAndGet
public final long addAndGet(long delta)

Атомарно добавляет данное значение к текущей стоимости.

toString
public String toString()

Возвращает Строковое представление текущей стоимости.

intValue
public int intValue()

Описание скопировало с класса: Number
Возвращает значение конкретного количества как int . Это может включить округление или усечение.

longValue
public long longValue()

Описание скопировало с класса: Number
Возвращает значение конкретного количества как a long . Это может включить округление или усечение.

floatValue
public float floatValue()

Описание скопировало с класса: Number
Возвращает значение конкретного количества как a float . Это может включить округление.

doubleValue
public double doubleValue()

Atomic classes

Пусть есть класс, который увеличивает значение счетчика от 0 до 500:

public class AtomicTest implements Runnable < Integer counter = 0; public void run() < for (int i = 0; i < 500; i++) < counter++; >> >

Мы хотим запустить его в двух потоках:

public class AtomicTest implements Runnable < Integer counter = 0; public void run() < for (int i = 0; i < 500; i++) < counter++; >> public static void main(String[] args) throws InterruptedException < AtomicTest atomicTest = new AtomicTest(); Thread thread1 = new Thread(atomicTest); Thread thread2 = new Thread(atomicTest); thread1.start(); thread2.start(); //ждем секунду, чтобы дождаться завершения потоков, а потом напечатать результат Thread.sleep(1000); System.out.println(atomicTest.counter); >>

Казалось бы, один поток увеличивает счетчик 500 раз, второй еще 500 раз, а значит результат должен быть 1000. Но нет.

Результат выполнения вышеприведенной программы непредсказуем. Он всегда меньше 1000, у меня выводятся такие значения при нескольких запусках:

685 611 849

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

int temp=counter; //1. чтение counter = temp + 1; // 2. добавление единицы 3. запись

И поскольку count++ не синхронизирован, ничто не запрещает войти в этот участок двум потока одновременно. Они могут считать одно и то же значение, а потом добавить к нему 1. Например, оба потока могут сначала считать значение 0, а потом увеличить его на 1, так что итоговым результатом будет 1, а не 2.

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

Вариант c AtomicInteger

Чтобы сделать операцию атомарной, обернем счетчик counter в AtomicInteger:

public class AtomicTest implements Runnable < AtomicInteger counter = new AtomicInteger(0); public void run() < for (int i = 0; i < 500; i++) < counter.getAndIncrement(); System.out.println(counter); >> public static void main(String[] args) throws InterruptedException < AtomicTest atomicTest = new AtomicTest(); Thread thread1 = new Thread(atomicTest); Thread thread2 = new Thread(atomicTest); thread1.start(); thread2.start(); Thread.sleep(1000); System.out.println(atomicTest.counter); >>

Обратите внимание, что увеличение счетчика делается с помощью метода:

counter.getAndIncrement();

Он имеет ту же функциональность, что и синхронизация:

synchronized (this)

То есть все потоки по очереди увеличивают значение, два потока одновременно в этот участок не зайдут.

Теперь результат верный, 1000.

Но если добавить в цикл System.out.println(counter) можно заметить, что числа выводятся не по порядку (хоть итоговый результат и верный):

. 932 899 933 935 936 934 938 . 1000

Это происходит потому, что System.out.println(counter) уже не входит в синхронизированный участок кода. Синхронизируется только counter++.

Чтобы сделать вывод чисел строго последовательным, придется применить synchronized.

Вариант с synchronized

Здесь как увеличение счетчика, так и вывод в консоль чисел находится внутри блока synchronized:

public class AtomicTest implements Runnable < Integer counter = 0; public void run() < for (int i = 0; i < 500; i++) < synchronized (this) < counter++; System.out.println(counter); >> > public static void main(String[] args) throws InterruptedException < AtomicTest atomicTest = new AtomicTest(); Thread thread1 = new Thread(atomicTest); Thread thread2 = new Thread(atomicTest); thread1.start(); thread2.start(); Thread.sleep(1000); System.out.println(atomicTest.counter); >>

В результате числа выводятся строго по порядку, результат тоже 1000.

1 2 . 1000

Итоги

Мы рассмотрели AtomicInteger. По функционалу его метод:

counter.getAndIncrement();

равносилен синхронизированному коду:

synchronized (object)

Код примера есть на GitHub.

Автор sysout Опубликовано 04.10.2021 04.10.2021 Рубрики Core Java

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

Прошу прощения: на комментарии временно не отвечаю.

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

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

С точки зрения программиста операции инкремента (i++, ++i) и декремента (i—, —i) выглядят наглядно и компактно. Но, с точки зрения JVM (виртуальной машины Java) данные операции не являются атомарными, поскольку требуют выполнения нескольких действительно атомарных операции: чтение текущего значения, выполнение инкремента/декремента и запись полученного результата. При работе в многопоточной среде операции инкремента и декремента могут стать источником ошибок. Т.е. в многопоточной среде простые с виду операции инкремента и декремента требуют использование синхронизации и блокировки. Но блокировки содержат массу недостатков, и для простейших операций инкремента/декремента являются тяжеловесными. Выполнение блокировки связано со средствами операционной системы и несёт в себе опасность приостановки с невозможностью дальнейшего возобновления потока, а также опасность взаимоблокировки или инверсии приоритетов (priority inversion). Кроме этого, появляются дополнительные расходы на переключение потоков. Но можно ли обойтись без блокировок? В ряде случаев можно!

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

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

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

Каждый атомарный класс включает метод compareAndSet, представляющий механизм оптимистичной блокировки и позволяющий изменить значение value только в том случае, если оно равно ожидаемому значению (т.е. current). Если значение value было изменено в другом потоке, то оно не будет равно ожидаемому значению. Следовательно, метод compareAndSet вернет значение false, что приведет к новой итерации цикла while в методе getAndAdd. Таким образом, в очередном цикле в переменную current будет считано обновленное значение value, после чего будет выполнено сложение и новая попытка записи получившегося значения (т.е. next). Переменные current и next — локальные, и, следовательно, у каждого потока свои экземпляры этих переменных.

private volatile long value; public final long get() < return value; >public final long getAndAdd(long delta) < while (true) < long current = get(); long next = current + delta; if (compareAndSet(current, next)) return current; >>

Метод 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 реализованы на уровне машинных инструкций, а блокировки тяжеловесны и используют приостановку и возобновление потоков, переключение контекста и т.д. Тем не менее, блокировки демонстрируют лучший результат только при очень «высокой конкуренции», что в реальной жизни встречается не так часто.

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

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

Рассмотрим генерирующий последовательность [1, 2, 4, 8, 16, . ] класс SequenceGenerator, функционирующий в многопоточной среде.

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

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

import java.math.BigInteger; import java.util.concurrent.atomic.AtomicReference; public class SequenceGenerator < private static BigInteger MULTIPLIER; private AtomicReferenceelement; public SequenceGenerator() < if (MULTIPLIER == null) MULTIPLIER = BigInteger.valueOf(2); element = new AtomicReference( BigInteger.ONE); > public BigInteger next() < BigInteger value; BigInteger next; do < value = element.get(); next = value.multiply(MULTIPLIER); >while (!element.compareAndSet(value, next)); return value; > >
Листинг последовательности Sequence

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

import java.math.BigInteger; import java.util.ArrayList; import java.util.List; class Sequence implements Runnable < Thread thread; int id; int count; SequenceGenerator sg; Listsequence; sequence = new ArrayList(); boolean printed = false; Sequence(final int id, final int count, SequenceGenerator sg) < this.count = count; this.id = id; this.sg = sg; thread = new Thread(this); System.out.println("Создан поток " + id); thread.start(); >@Override public void run() < try < for (int i = 0; i < count; i++) < sequence.add(sg.next()); Thread.sleep((long) ( (Math.random()*2 + 1)*30)); >> catch (InterruptedException e) < System.out.println("Поток " + id + " прерван"); >System.out.print("Поток " + id + " завершён"); printSequence(); > public void printSequence() < if (printed) return; String tmp = "["; for (int i = 0; i < sequence.size(); i++) < if (i >0) tmp += ", "; String nb = String.valueOf(sequence.get(i)); while (nb.length() < 9) nb = " " + nb; tmp += nb; >tmp += "]"; System.out.println("Последовательность потока " + id + " : " + tmp); printed = true; > >
Листинг примера SequenceGeneratorExample

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

public class SequenceGeneratorExample < public static void main(String[] args) < SequenceGenerator sg = new SequenceGenerator(); Listsequences = new ArrayList(); for (int i = 0; i < 10; i++) < Sequence seq = new Sequence(i + 1, 3, sg); sequences.add(seq); >System.out.println("\nРасчет последовательностей\n"); int summa; // Ожидания завершения потоков do < summa = 0; for (int i = 0; i < sequences.size(); i++) < if (!sequences.get(i).thread.isAlive()) < sequences.get(i).printSequence(); summa++; >> try < Thread.sleep(100); >catch (InterruptedException e) <> > while (summa < sequences.size()) ; System.out.println("\n\nРабота потоков завершена"); System.exit(0); >>
Результаты выполнения примера

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

Создан поток 0 Создан поток 1 Создан поток 2 Создан поток 3 Создан поток 4 Создан поток 5 Создан поток 6 Создан поток 7 Создан поток 8 Создан поток 9 Расчет последовательностей Поток 7 завершён Последовательность потока 7 : [ 256, 4096, 524288] Поток 5 завершён Поток 4 завершён Поток 1 завершён Последовательность потока 1 : [ 2, 1024, 2097152] Последовательность потока 4 : [ 16, 8192, 8388608] Последовательность потока 5 : [ 64, 2048, 32768] Поток 9 завершён Поток 3 завершён Поток 6 завершён Последовательность потока 3 : [ 8, 131072, 134217728] Последовательность потока 6 : [ 32, 16384, 268435456] Последовательность потока 9 : [ 512, 262144, 16777216] Поток 0 завершён Поток 2 завершён Поток 8 завершён Последовательность потока 0 : [ 1, 65536, 67108864] Последовательность потока 2 : [ 4, 1048576, 33554432] Последовательность потока 8 : [ 128, 4194304, 536870912] Работа потоков завершена

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

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

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

Хотите быстрее с AtomicLong?

Я часто слышу, что атомарные типы Java (java.util.concurrent.atomic) супербыстрые и прекрасно работают с высококонкурентным кодом. Большую часть времени атомщики выполняют свою работу надежно и эффективно. Однако есть сценарии, в которых скрытая стоимость неуправляемой конкуренции за атомарные типы становится серьезной проблемой производительности. Давайте посмотрим, как реализованы типы java.util.concurrent.atomic.Atomic * и что подразумевает этот дизайн.

Все атомарные типы, такие как AtomicLong, AtomicBoolean, AtomicReference и т. Д., По сути являются обертками для изменчивых значений. Дополнительную ценность дает внутреннее использование sun.misc.Unsafe, которое предоставляет возможности CAS для этих типов.

CAS (сравнение и замена) по сути является атомарной инструкцией, реализованной современным аппаратным обеспечением ЦП, которая позволяет неблокирующим, многопоточным манипулировать данными безопасным и эффективным способом. Огромным преимуществом CAS перед блокировкой является тот факт, что CAS не несет никаких накладных расходов на уровне ядра, поскольку никакого арбитража не происходит. Вместо этого компилятор выдает инструкции процессора, такие как lock cmpxchg, lock xadd, lock addq и т. Д. Это так же быстро, как вы можете получить, вызывая инструкции с точки зрения JVM.

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

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

Я воспроизвел некоторые концепции из бумаги и проверил их. Многие Java-программисты должны находить результаты достаточно показательными, поскольку существует распространенное заблуждение относительно производительности атомарного (CAS).

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

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

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