Явкие примитивные атомы по конструкции или случайно?

Являются ли примитивные целые числа java (int) атомами вообще, если на то пошло? Некоторые эксперименты с двумя streamами, совместно использующими int, как представляется, указывают на то, что они есть , но, конечно, отсутствие доказательств того, что они не являются, не означает, что они есть.

В частности, тест, который я запускал, был следующим:

public class IntSafeChecker { static int thing; static boolean keepWatching = true; // Watcher just looks for monotonically increasing values static class Watcher extends Thread { public void run() { boolean hasBefore = false; int thingBefore = 0; while( keepWatching ) { // observe the shared int int thingNow = thing; // fake the 1st value to keep test happy if( hasBefore == false ) { thingBefore = thingNow; hasBefore = true; } // check for decreases (due to partially written values) if( thingNow < thingBefore ) { System.err.println("MAJOR TROUBLE!"); } thingBefore = thingNow; } } } // Modifier just counts the shared int up to 1 billion static class Modifier extends Thread { public void run() { int what = 0; for(int i = 0; i < 1000000000; ++i) { what += 1; thing = what; } // kill the watcher when done keepWatching = false; } } public static void main(String[] args) { Modifier m = new Modifier(); Watcher w = new Watcher(); m.start(); w.start(); } } 

(и это было сделано только с java jre 1.6.0_07 на 32-битном Windows-ПК)

По существу, модификатор записывает последовательность счетчиков в общее целое число, а Watcher проверяет, что наблюдаемые значения никогда не уменьшаются. На машине, где 32-битное значение должно было быть доступно в виде четырех отдельных байтов (или даже двух 16-ти битных слов), была бы вероятность того, что Watcher поймает общее целое в несогласованном, частично обновленном состоянии и обнаружит уменьшение значения а не увеличиваться. Это должно работать независимо от того, собираются ли (гипотетические) байты данных, записываются LSB 1st или MSB 1st, но в лучшем случае они скорее всего вероятны.

Казалось бы, очень вероятно, учитывая сегодняшние широкие пути данных, что 32-битное значение может быть эффективно атомным, даже если спецификация java не требует этого. Фактически, с 32-битной шиной данных кажется, что вам, возможно, придется больше работать, чтобы получить атомный доступ к байтам, чем к 32-битным int.

Googling на тему «java primitive thread safety» отображает множество вещей в streamобезопасных classах и объектах, но поиск информации о примитивах, похоже, ищет иглу пословиц в стоге сена.

Все обращения к памяти в Java по умолчанию являются атомарными, за исключением long и double (которые могут быть атомарными, но не обязательно). Честно говоря, это не очень ясно, но я считаю, что это следствие.

Из раздела 17.4.3 JLS:

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

а затем в 17.7 :

В некоторых реализациях может оказаться удобным разделить одно действие записи на 64-битное длинное или двойное значение на два действия записи при смежных 32-битных значениях. Для эффективности это поведение специфично для реализации; Виртуальные машины Java могут выполнять запись в длинном и двойном значениях атомарно или в двух частях.

Обратите внимание, что атомарность сильно отличается от волатильности.

Когда один stream обновляет целое число до 5, гарантируется, что другой stream не увидит 1 или 4 или какое-либо другое промежуточное состояние, но без какой-либо явной волатильности или блокировки другой stream может видеть 0 навсегда.

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

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

Другими словами, для JVM это правильно.

  • Никакое количество испытаний не может доказать безопасность streamов – это может только опровергнуть это;
  • Я нашел косвенную ссылку в JLS 17.7, в которой говорится

В некоторых реализациях может оказаться удобным разделить одно действие записи на 64-битное длинное или двойное значение на два действия записи при смежных 32-битных значениях.

и далее вниз

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

Это, по-видимому, означает, что записи в ints являются атомарными.

Я думаю, что он не работает, как вы ожидали:

 private static int i = 0; public void increment() { synchronized (i) { i++; } } 

integer неизменен, поэтому вы все время синхронизируете на другом объекте. int «i» автоматически добавляется к объекту Integer, после чего вы устанавливаете его блокировку. Если в этот метод входит другой stream, то int i автоматически добавляется к другому объекту Integer, а затем блокирует другой объект.

Я согласен с Джоном Скитом, и я хотел бы добавить, что многие люди путают понятие атомарности, волатильности и безопасности streamов, потому что иногда термины используются взаимозаменяемо.
Например, рассмотрим следующее:

 private static int i = 0; public void increment() { i++; } 

Хотя кто-то может утверждать, что эта операция является атомарной, упомянутая гипотеза неверна.
Утверждение i++; выполняет три операции:
1) Прочитайте
2) Обновление
3) Напишите

Поэтому streamи, которые работают с этой переменной, должны быть синхронизированы следующим образом:

 private static int i = 0; private static final Object LOCK = new Object(); public void increment() { synchronized(LOCK) { i++; } } 

или это:

 private static int i = 0; public static synchronized void increment() { i++; } 

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

Для получения дополнительной информации ознакомьтесь с этой ссылкой:
http://www.javamex.com/tutorials/synchronization_volatile.shtml

Надеюсь это поможет.

ОБНОВЛЕНИЕ : существует также случай, когда вы можете синхронизировать сам объект classа. Подробнее здесь: Как синхронизировать статическую переменную среди streamов, работающих с разными экземплярами classа в java?

Чтение или запись из целого числа или любого более мелкого типа должно быть атомарным, но, как заметил Роберт, длинные и удвоенные значения могут или не зависят от реализации. Однако любая операция, которая использует как чтение, так и запись, включая все операторы приращения, не является атомарной. Таким образом, если у вас есть streamи, работающие на целое число i = 0, то i ++, а другой i = 10, результат может быть 1, 10 или 11.

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

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

Это не атомный:

 i++; 

Однако это:

 i = 5; 

Я думаю, что здесь возникает какая-то путаница.

Это несколько сложно и связано с системной словесностью. Брюс Эккель обсуждает это более подробно: Java Threads .

Атомные чтения и записи просто означают, что вы никогда не прочтете, например, первые 16 бит обновления int, а другое – из старого значения.

Это ничего не говорит о том, КОГДА другие streamи видят эти записи.

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

Сдвиньте два или более streamа, которые увеличивают единое целое число и также рассчитывают их собственные приращения. Когда целое число получает какое-то значение (например, INT_MAX. Приятно и сильно, чтобы вещи разогревались) остановите все и верните значение int и количество приращений каждого выполняемого streamа.

  import java.util.Stack; public class Test{ static int ctr = Integer.MIN_VALUE; final static int THREADS = 4; private static void runone(){ ctr = 0; Stack threads = new Stack<>(); for(int i = 0; i < THREADS; i++){ Thread t = new Thread(new Runnable(){ long cycles = 0; @Override public void run(){ while(ctr != Integer.MAX_VALUE){ ctr++; cycles++; } System.out.println("Cycles: " + cycles + ", ctr: " + ctr); } }); t.start(); threads.push(t); } while(!threads.isEmpty()) try{ threads.pop().join(); }catch(InterruptedException e){ // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(); } public static void main(String args[]){ System.out.println("Int Range: " + ((long) Integer.MAX_VALUE - (long) Integer.MIN_VALUE)); System.out.println(" Int Max: " + Integer.MAX_VALUE); System.out.println(); for(;;) runone(); } } в  import java.util.Stack; public class Test{ static int ctr = Integer.MIN_VALUE; final static int THREADS = 4; private static void runone(){ ctr = 0; Stack threads = new Stack<>(); for(int i = 0; i < THREADS; i++){ Thread t = new Thread(new Runnable(){ long cycles = 0; @Override public void run(){ while(ctr != Integer.MAX_VALUE){ ctr++; cycles++; } System.out.println("Cycles: " + cycles + ", ctr: " + ctr); } }); t.start(); threads.push(t); } while(!threads.isEmpty()) try{ threads.pop().join(); }catch(InterruptedException e){ // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(); } public static void main(String args[]){ System.out.println("Int Range: " + ((long) Integer.MAX_VALUE - (long) Integer.MIN_VALUE)); System.out.println(" Int Max: " + Integer.MAX_VALUE); System.out.println(); for(;;) runone(); } } 

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

 Int Range: 4294967295 Int Max: 2147483647 Cycles: 2145700893, ctr: 76261202 Cycles: 2147479716, ctr: 1825148133 Cycles: 2146138184, ctr: 1078605849 Cycles: 2147282173, ctr: 2147483647 Cycles: 2147421893, ctr: 127333260 Cycles: 2146759053, ctr: 220350845 Cycles: 2146742845, ctr: 450438551 Cycles: 2146537691, ctr: 2147483647 Cycles: 2110149932, ctr: 696604594 Cycles: 2146769437, ctr: 2147483647 Cycles: 2147095646, ctr: 2147483647 Cycles: 2147483647, ctr: 2147483647 Cycles: 2147483647, ctr: 330141890 Cycles: 2145029662, ctr: 2147483647 Cycles: 2143136845, ctr: 2147483647 Cycles: 2147007903, ctr: 2147483647 Cycles: 2147483647, ctr: 197621458 Cycles: 2076982910, ctr: 2147483647 Cycles: 2125642094, ctr: 2147483647 Cycles: 2125321197, ctr: 2147483647 Cycles: 2132759837, ctr: 330963474 Cycles: 2102475117, ctr: 2147483647 Cycles: 2147390638, ctr: 2147483647 Cycles: 2147483647, ctr: 2147483647 
  • Вызывающий stream должен быть STA, потому что многие компоненты пользовательского интерфейса требуют этого
  • как установить близость процессора к определенному pthread?
  • Убедитесь, что OnPropertyChanged () вызывается в streamе пользовательского интерфейса в приложении MVVM WPF
  • Ошибка ContextSwitchDeadlock была обнаружена в C #
  • Dispatcher Invoke (...) против BeginInvoke (...) путаницы
  • Недопустимая проблема с доступом к нескольким streamам
  • Несколько клиентов одновременно получают доступ к серверу
  • Java: notify () vs. notifyAll () снова и снова
  • Передача функций-членов в std :: thread
  • Что делает java.lang.Thread.interrupt ()?
  • COM-объект, который был отделен от его базового RCW, не может быть использован
  • Давайте будем гением компьютера.