Есть True и False sharing которые позволяет процессорам обмениваться кэш-линиями. Как при этом может существовать проблема visibility? Если sharing позволяет ядрам видеть кэши друг друга, то в чем тогда проблема которую решает volatile? Или можно еще перефразировать так: чего не хватает sharing механизмам, чтобы в состоянии контролируемой гонки предотвратить утечку данных? Возможно, что sharing-то работает, но он работает только для тех процессоров, которые уже имеют данную переменную в кэше, а потоки-новобранцы могут прочитать данные из памяти, которые уже не актуальны, так как те потоки, что уже давно работают с этой переменной — успели изменить ее значение после последней выгрузки в память? То есть в промежутке между первым считыванием из памяти переменной и её первого изменения шаринг не срабатывает? (как гипотеза). Update: "если протокол согласованности кэшей (cache coherency) обязывает кэши процессора хранить ячейку памяти в согласованном состоянии, то зачем нужен volatile, который делает то же самое?"
Ответ False Sharing False Sharing это термин, описывающий механизм нежелательного снижения производительности, когда разные потоки модифицируют независимые переменные, которые оказались на одной кеш линии. Прочитайте замечательную стататью с Хабра, где описаны эти механизмы. Если кратко, то вот цитата: При этом, если один из потоков модифицирует поле своей структуры, то вся кэш-линия в соответствии с cache coherency протоколом объявляется невалидной для остальных ядер процессора. Другой поток уже не сможет пользоваться своей структурой, несмотря на то, что она уже лежит в L1 кэше его ядра. В старых процессорах типа P4 в такой ситуации потребовалась бы долгая синхронизация с основной памятью, то есть модифицированные данные были бы отправлены в основную память и потом считаны в L1 кэш другого ядра. Volatile Модификатор volatile это ключево слово в языке java, введенное в язык для поддержки безопасного многопоточного программирования. Оно накладывает некоторые дополнительные условия на чтение/запись переменной. Важно понять три вещи о volatile переменных: Операции чтения/записи volatile переменной являются атомарными. Результат операции записи значения в volatile переменную одним потоком, становится виден всем другим потокам, которые используют эту переменную для чтения из нее значения. Ключевое слово volatile запрещает некоторые оптимизации/перестановки в процессоре и/или компиляторе. Т.е. сравнивать эти понятия не совсем корректно ибо volatile это слово для реализации безопасных многопоточных пограмм, а false sharing это термин описывающий деградацию производительности. Посмотрите замечательные лекции от Алексея Шипилева по java memory model (и не только), где он все раскладывает по полочкам. Если у Вас будут вопросы, то я могу попробовать раскрыть обновляя свой ответ. UPD: Ответы на вопросы внизу. а где в спеке написано что volatile гарантирует нам атомарность операций? Ссылки: Essentials от Oracle, спецификация Разве не для того мы ставим synchronize чтобы предотвратить последствия не атомарности? Если бы volatile гарантировал атомарность то он бы один решал все проблемы, так получается или нет? Важно понять, что существуют два аспекта потокобезопасности: (1) контроль выполнения и (2) видимость памяти. Первый отвечает за контроль выполнения кода (включая порядок инструкций) и разрешая/запрещая некоторым блокам программы возможность выполняться конкурентно (concurrently /одновременно). Второе какие действия с памятью видны или не видны для других потоков. Это вызвано тем, что каждый процессор имеет несколько уровней кеша между самим процессором и общей памятью, поэтому потоки запущенные на разных ядрах процессора могут видеть "разную память" в один и тот же момент из-за локального кеша процессора. Synchronized Использование synchronized не позволяет другому процессу захватить монитор (или lock) на том же самом обьекте, таким образом препятствуя конкуррентному (одновременному) выполнению кода, заключенного в synchronized блок. Важно учесть, что синхронизация создает так называемое отношение happens-before. Это отношение позволяет потоку захватившему монитор "увидеть" все изменения, сделанные другим потоком до захвата и отпускания (release) монитора. На практике же это будет соответствовать (грубое приближение) тому, что процессор будет обновлять кеши в момент захвата монитора и записывать в память после его освобождения. Эти оперции довольно долгие (относительно). Volatile Ипользование volatile заставляет делать операции с переменной используя память програмы "минуя" кеш процессора. Это может быть полезно, когда нам нужна видимость этой переменной в разных потоках, но при этом нам не важен парядок доступа к данной переменной. Также на 32bit java запись long & double становится атомарной при обьявлении переменной как volatile. В новой спецификации JSR-133 (в Java5) семантику volatile усилили. На нее наложили правила видимости и правила запрещающие некоторые опитимизации компилятора/jvm. Примеры Volatile - поможет Предположим у нас есть какой-то неизменяемый обьект, ссылка на который доступна для множества потоков, и они постоянно используют его в своих вычилениях. Volatile отлично подходит для данной ситуации. Необходимо, чтобы другие потоки стали использовать новый обьект как только он будет обьявлен (в данном месте имею ввиду, что мы поменяем ссылку с существующего обьекта на сконтруированный новый). При этом нам не требуется специльно синхронизировать это обновление, сбрасывать кеши. Volatile - не поможет Возьмем обычный счетчик: volatile int counter = 0 public void update() { counter++ //или counter = counter + 1 } Операция инкремента неатомарна и состоит из трех операций: чтение, инкремент, запись. В данном примере может случиться ситуация, когда: Поток1: заходит в метод читает значение "0" Поток1: увеличивает значение на единицу "1" Выполнение переходит к второму потоку Поток2: чтение значения "0" Поток2: увеличение значения на единицу "1" Поток2: запись "1" в counter Выполнение переходит к первому потоку Поток1: запись "1" в counter В результате вместо значения "2" в счетчике хранится значение "1". В данном случаче поможет синхронизация метода update() или использование AtomicInteger и т.д. это уже за пределами данного вопроса. Подытоживая все вышескзанное - volatile переменные используются, когда все операции происходящие с обьектом "атомарны" как в первом примере (меняется ссылка на полностью сформированный обьект, идет запись из одного одного потока) и нет конкуренции за состояние обьекта.