Что означает «атомарный» в программировании?

276

В книге «Эффективная Java» говорится:

Спецификация языка гарантирует, что чтение или запись переменной является атомарным, если переменная не имеет тип longили double[JLS, 17.4.7].

Что означает «атомарный» в контексте программирования на Java или программирования в целом?

Джеймс
источник
24
Одна операция за раз.
Subhrajyoti Majumder
1
только одна операция может быть выполнена с переменной за раз.
Кайсуш
1
я подозреваю, что вопросы философии принадлежат codereview.stackexchange.com
Phlip
Отмечая, что некоторые переменные по умолчанию не имеют атомарного чтения и записи, объявляют их как volatile longили volatile doubleделают чтение атомарным и запись атомарным.
H2ONaCl

Ответы:

372

Вот пример, потому что пример часто яснее, чем длинное объяснение. Предположим foo, это переменная типа long. Следующая операция не является атомарной операцией:

foo = 65465498L;

Действительно, переменная записывается с использованием двух отдельных операций: одна записывает первые 32 бита, а вторая - последние 32 бита. Это означает, что другой поток может прочитать значение fooи увидеть промежуточное состояние.

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

Простой способ сделать это - сделать переменную volatile :

private volatile long foo;

Или синхронизировать каждый доступ к переменной:

public synchronized void setFoo(long value) {
    this.foo = value;
}

public synchronized long getFoo() {
    return this.foo;
}
// no other use of foo outside of these two methods, unless also synchronized

Или заменить его на AtomicLong:

private AtomicLong foo;
Дж. Б. Низет
источник
75
Так что это предполагает, что он работает в 32-битной системе. Что если это была 64-битная система? Will foo = 65465498L; быть атомным тогда?
Harke
46
@ Harke Если у вас 64-битная Java, то да.
Йерун
4
Это относится и к C # и .NET тоже? Если да, для того, чтобы foo получил атомарное поведение, CLR должен быть 64-битным?
Фабиано
5
@Fabiano Это применимо, и вот как это сделать в .NET, поскольку у нас нет синхронизированного ключевого слова, такого как Java. stackoverflow.com/questions/541194/…
The Muffin Man
2
Тогда давайте предположим, что поток A назначает длинное значение, а затем половина его пытается прочитать. Если операция A является атомарной, то поток B будет ждать ее завершения? Это означает, что атомарные операции обеспечат неявную безопасность потоков?
Теоман Шипахи
60

«Атомная операция» означает операцию, которая кажется мгновенной с точки зрения всех других потоков. Вам не нужно беспокоиться о частично завершенной операции, когда действует гарантия.

H2ONaCl
источник
25

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

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

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

Ваша цитата подчеркивает, что такого поведения не следует ожидать во всех случаях.

Грант Томас
источник
15

Только что обнаружил, что пост « Атомные и неатомные операции» мне очень помог.

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

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

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

Курт Чжун
источник
14

Если у вас есть несколько потоков, выполняющих методы m1 и m2 в коде ниже:

class SomeClass {
    private int i = 0;

    public void m1() { i = 5; }
    public int m2() { return i; }
}

у вас есть гарантия, что любой вызывающий поток m2будет читать 0 или 5.

С другой стороны, с этим кодом (где iдлинный):

class SomeClass {
    private long i = 0;

    public void m1() { i = 1234567890L; }
    public long m2() { return i; }
}

вызывающий поток m2может прочитать 0, 1234567890L или другое случайное значение, потому что оператор i = 1234567890Lне гарантированно является атомарным для long(JVM может записать первые 32 бита и последние 32 бита в двух операциях, а поток может наблюдать iмежду ними) ,

assylias
источник
почему вы думаете, что «long» вызывает проблемы, а «int» - нет? Пожалуйста , смотрите здесь geekswithblogs.net/BlackRabbitCoder/archive/2012/08/09/...
onmyway133
1
@entropy длинные и двойные назначения не гарантированно являются атомарными в Java. Таким образом, вы можете прочитать длинный, где только половина битов была обновлена ​​после назначения.
assylias
0

В Java поля чтения и записи всех типов, кроме long и double, выполняются атомарно, и если поле объявляется с модификатором volatile, даже long и double атомарно считываются и записываются. То есть мы получаем 100% либо того, что там было, либо что там произошло, и не может быть никакого промежуточного результата в переменных.

Евгений Шамкин
источник