Начну с того, что (как мне кажется) понимаю, а потом задам вопросы, если позволите .
Lucker пишет:
настаиваю на не путать атомарность и потокобезопасность!
Я не знаю, есть ли классическое определение атомарности (наверняка есть), но я всегда думал о нем так: если операция атомарна, то, какой бы продолжительной она не была, результат операции можно точно предсказать в момент ее начала. Атомарность гарантируется средой исполнения (ОС, СУБД, VM, ect.).
Например, атомарной может быть операция записи массива байт в файл. Она занимает какое-то время, но, используя ее, я могу быть уверен, что во время записи массива другие потоки не смогут писать в тот же файл. Иными словами, никакой другой процесс не сможет повлиять на результат операции. А значит в начале выполнения операции я точно знаю результат (возможность аппаратного сбоя не рассматривается). При этом ОС может отдать процессор другому процесу, прервав мой в середине операции записи (Пример немного не реальный, но от него этого и не требуется ).
Потокобезопасность. Ее я понимаю так: потокобезопасным кодом я называю такой, который для каждого потока даст результат, который бы они получили, работая по одному (например, последовательно). Т.е., независимо от того, сколько потоков исполняют код (или используют общие данные), они не влияют друг на друга.
Теперь по теме наконец (если еще кто-то читает):
— JSL,SE. Ищем упоминание атомарности и потокобезопасности и все, что на это похоже.
JLS, SE:17.1 Terminology and Framework
A variable is any location within a program that may be stored into. <skip> Variables are kept in a main memory that is shared by all threads. <skip> Every thread has a working memory in which it keeps its own working copy of variables that it must use or assign. As the thread executes a program, it operates on these working copies. The main memory contains the master copy of every variable. <skip> the verbs use, assign, load, store, lock, and unlock name actions that a thread can perform. The verbs read, write, lock, and unlock name actions that the main memory subsystem can perform. Each of these actions is atomic (indivisible).
Получается, согласно спецификации, есть несколько атомарных операций, которые не являются ни частью языка Java, ни байткодами для JVM (однако, информация о них входит в обе спецификации). Важно, что хоть что-то атомарное есть.
Предположим есть код:
class Sample {
int a = 1, b = 2;
void hither() {
a = b;
}
void yon() {
b = a;
}
}
Поток, выполняя метод hither, должен сделать следующее:
1. read b — memory subsystem, читает значение "b" из общей памяти
2. load b — current thread, записывает значение "b" в свою локальную память
3. use b — current thread, ???
4. assign a — current thread, ???
5. store a — current thread, записывает значение "a" в свою локальную память
6. write a — memory subsystem, записать значение "a" в общую память
Насколько я понимаю, use означает любое использование, кроме присвоения результата. Подобным образом можно описать и инкремент:
class Sample {
int a = 1;
void hither() {
a++;
}
}
Вот так:
1. read a — memory subsystem, читает значение "a" из общей памяти
2. load a — current thread, записывает значение "a" в свою локальную память
3. use a — current thread, здесь это инкремент.
4. assign a — current thread, ???
5. store a — current thread, записывает значение "a" в свою локальную память
6. write a — memory subsystem, записать значение "a" в общую память
Т.е. шесть атомарных метаопераций (назовем их так), но атомарности инкремента нет. Если представить себе поток, который выполнил шаг 3 и был прерван. И второй поток, который выполняет этот же код и тоже дошел до шага 3. Оба скопировали из общей памяти значение переменной "a" равное "1". Дальше не важно, какой поток пройдет все 6 шагов первым. Первый поток сохранит значение "2" в общую память. Второй перезапишет результат первого своим результатом (тоже равным "2"). Этот результат подтверждается тестом, предложенным dshe.
Важно отметить тут, что шаг 6 может не выполняться сразу после шага 5. Ведь результат может быть использован дальше.
class Sample {
int a = 1, b = 7;
void hither() {
a++;
b = b + a;
}
}
Уф... Лирическое отступление:
Больше года назад (когда текущей была JLS, SE) в нескольких форумах и переписке с разными людьми я задавал вопросы, касающиеся последовательности выполнения, оптимизации во время выполнения, etc. Я считал (и считаю) главу 17 JLS SE слишком сложной и непонятно. Иногда даже противоречивой (Меня больше всего интересовали volatile переменные. О них ниже ). Как выяснилось, Sun признала, что их реализация JRE нарушает спецификацию в отношении volatile переменных. (Я где-то видел что-то типа теста, который это подтверждал, но сам я не проверял, так что — за что купил, за то продаю ).
Теперь по потокобезопасности.
class Sample {
int a = 1;
void hither() {
a++;
}
}
Если бы инкремент был атомарной операцией, то код был бы потокобезопасным (в свете всего сказанного выше). Но это не так (тест, предложенный dshe).
Lucker пишет:
A>Я может чего не так понимаю ...
ну это всего лишь значит что при выполнении таких операций в многопоточной среде ты не получишь "неожиданного" результата, как например в случае с long-ами, где старшие байты могут быть изменены в одном потоке, младшие в другом и в результате получится не рыба не мясо.
Поддерживаю Lucker. Я это понимаю так: Речь идет об метооперации "use". Допустим у нас есть код:
class Sample {
int a = 1, b = 7, c = 1;
void hither() {
c = b + a;
}
}
Согласно спецификации, это выглядит так:
1. read a — memory subsystem, читает значение "a" из общей памяти
2. load a — current thread, записывает значение "a" в свою локальную память
3. read b — memory subsystem, читает значение "b" из общей памяти
4. load b — current thread, записывает значение "b" в свою локальную память
5. use a,b — current thread, здесь это инкремент.
6. assign c — current thread, ???
7. store c — current thread, записывает значение "a" в свою локальную память
8. write c — memory subsystem, записать значение "a" в общую память
Вот шаг 5 — это сложение, которое атомарное . Повторюсь, это лишь моя версия событий. Так я понимаю спецификацию.
Первое замечание: здравый смысл говорит мне, что шаги 1 и 2 нельзя менять местами, но шаги 1 и 2 могут быть исполнены после шагов 3 и 4.
1. read b — memory subsystem, читает значение "b" из общей памяти
2. load b — current thread, записывает значение "b" в свою локальную память
3. read a — memory subsystem, читает значение "a" из общей памяти
4. load a — current thread, записывает значение "a" в свою локальную память
Эти две группы метаопераций не влияют друг на друга, не так ли?
Второе замечание: перед use не обязательно будет read и load. А после assign не всегда store и write. Т.е. копия переменной в рабочей области памяти текущего процесса не обязана синхронизироваться с мастер-копией после и перед каждой операции(ей). Как раз это позволяет хранить переменный потока в регистрах, например.
Теперь пара слов о volatile переменных. Что они могут и чего не могут. Выжимка из 17.7 Rules for Volatile Variables:
— Перед каждым использованием (use) значения переменных синхронизируются с мастер-копиями (read, load).
— После каждого использования (assign) значения переменных синхронизируются с мастер-копией (store, write).
— Последовательность синхронизации volatile переменных с мастер-копиями не может быть изменена. (Это относиться к одному потоку)
JLS, SE:
The load, store, read, and write actions on volatile variables are atomic, even if the type of the variable is double or long.
Делает ли модификатор volatile операции с переменной атомарным? Нет. Потокобезопасным? Тоже нет.
korostoff пишет:
Вилимо потому что последовательность
загрузка volatile n1 из master memory, инкремент, сохранение нового значения n1 в master memory
само по себе не атомарно.
В точку. Это тоже очень интересная тема. Многие, с кем я общался по проблеме volatile переменных (в том числе и я по началу) считали, что volatile — это что-то вроде "монитора для примитивов" . Т.е. я предполагал, что объявив переменную с модификатором volatile:
class TestVolatile {
private volatile int foo = 1;
public void up() {
foo++;
}
}
я получу такое поведение:
class TestVolatile {
private int foo = 1;
public synchronized void up() {
foo++;
}
}
Два потока вызывают метод. Тут все просто. Потоки синхронизируются на мониторе экземпляра TestVolatile. Многие считают, что первая версия (с использованием volatile) является способом "синхронизации на мониторе примитива". С примитивами не связаны мониторы и такая версия событий, что называется, reasonable. Но увы, это не так.
Подводя черту под volatile: все, что они могут делать это заменить один тип рассинхронизации при работе нескольких процессов на другой . Какой в этом смысл и к что на самом деле хотели сделать — я не знаю. Вот с этим и живу .
К сожалению, в деталях JMM JLS3 я не разбирался. Я прочитал jsr в надежде, что она станет проще и понятнее . Что могу сказать — она точно изменилась...
А теперь вопрос:
— Все что я писал вполне может оказаться бредом. Пожалуйста, поправьте меня.
— Поразмышляв еще немного над всем этим, можно прийти к выводу, что JLS не запрещает JVM переключать процессы в середине исполнения очередного байткода. Справедливо ли это для JLS3?
— Все эти сложности со спецификацией направлены на то, чтобы дать разработчикам JVM как можно больше свободы при реализации многопороцесности. Это понятно. А вот кто-нибудь может авторитетно заявить, как реализована многопроцесность в JVM для WIN32, например? Т.е. создание потока выполнения создает процесс или поток? Т.е. насколько это дорогая операция? А как с этим в Linux например?
Спасибо всем, кто дочитал . Большое спасибо тем, кто ответит !
Atomicity guarantees ensure that when a non-long/double field is used in an expression, you will obtain either its initial value or some value that was written by some thread, but not some jumble of bits resulting from two or more threads both trying to write values at the same time. However, as seen below, atomicity alone does not guarantee that you will get the value most recently written by any thread. For this reason, atomicity guarantees per se normally have little impact on concurrent program design.
Volatile
In terms of atomicity, visibility, and ordering, declaring a field as volatile is nearly identical in effect to using a little fully synchronized class protecting only that field via get/set methods, as in:
final class VFloat {
private float value;
final synchronized void set(float f) { value = f; }
final synchronized float get() { return value; }
}
Declaring a field as volatile differs only in that no locking is involved. In particular, composite read/write operations such as the "++'' operation on volatile variables are not performed atomically.
Кстати, твой пример (второй), естественно, тоже не проходит. Если он у тебя проходит, то вполне возможно, что там просто однопроцесорная машина (и без HT).
Здравствуйте, aefimov, Вы писали:
A>Т.е. теперь я постараюсь объяснить почему volatile на i++ не нужен и операция является thread-safe, как и остальные атомарные действия.
A>Все, бейте!
Думаю, что неатомарность обычного инкремента может доказать следующий эксперимент.
Давайте попробуем из нескольких потоков вызывать инкремент shared переменной. Если инкремент атомарен, то эта переменная увеличится на число равное количеству потоков умноженное на количество раз, на которое каждый поток попытался ее увеличить. Если инкремент неатомарен, то возможны "пропуски" и переменная увеличится на меньшее число.
public class Main implements Runnable {
private static final int ITERATIONS = 100000;
private static final int THREADS = 10;
private/*volatile*/int _counter = 0;
private boolean _start = false;
public static void main(String[] args) {
Main main = new Main();
Thread[] threads = new Thread[THREADS];
for (int i = 0; i < THREADS; ++i) {
threads[i] = new Thread(main);
threads[i].start();
}
main.go();
for (int i = 0; i < THREADS; ++i) {
try {
threads[i].join();
} catch (InterruptedException exc) {
throw new RuntimeException(exc);
}
}
System.out.println("actual : " + main._counter);
System.out.println("expected : " + (ITERATIONS * THREADS));
System.out.println("passed : " + (main._counter == (ITERATIONS * THREADS)));
}
public void run() {
barrier();
for (int i = 0; i < ITERATIONS; ++i) {
// synchronized(this) {
++_counter;
// }
}
}
private synchronized void barrier() {
while (!_start) {
try {
wait();
} catch (InterruptedException exc) {
throw new RuntimeException(exc);
}
}
}
private synchronized void go() {
_start = true;
notifyAll();
}
}
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, korostoff, Вы писали:
K>>Да, если переменная не volatile или если между запись в одном потоке/чтение в другом не было memory barrier (т.е. если после записи не освобожден монитор, а перед чтением не получен ТОТЖЕ монитор).
А>Тут я вас не понял. Почему именно "тотже" монитор? Поясните пожалуйста
Я пытался . Видимо лучше меня это объяснено в JSR133 FAQ: What does synchronization do? http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html#synchronization
А>Вот здесь тоже есть разногласия в понимании JLS . Вы пишите "ВСЕ иземененные переменные", а есть мнение, что только те, к которым относиться замок. Иными словами, освобождения замка объекта приводит к записи данных объекта в общую память. А если процесс менял данные других объектов?
Цитата из JLS 2.0 "17.6 Rules about the Interaction of Locks and Variables"
...if a thread is to perform an unlock action on any lock, it must first copy all assigned values in its working memory back out to main memory.
...
a lock action acts as if it flushes all variables from the thread's working memory; before use they must be assigned or loaded from main memory.
Здравствуйте, korostoff, Вы писали:
K>Даже если i++ атомарна то без объявления i с модификатором volatile вся это атомарность может пойти лесом, т.к. другой поток может просто
i++ даже с volatile не потоко безопасна. Кстати, рекоммендую посмотреть на (и пользоваться) java.util.concurrent.
Например, в AtomicInteger инкремент реализован так (приведены только важные части класса):
public class AtomicInteger extends Number implements java.io.Serializable {
// setup to use Unsafe.compareAndSwapInt for updatesprivate static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch(Exception ex) { throw new Error(ex); }
}
private volatile int value;
/**
* Get the current value.
*
* @return the current value
*/public final int get() {
return value;
}
/**
* Atomically set the value to the given updated value
* if the current value <tt>==</tt> the expected value.
* @param expect the expected value
* @param update the new value
* @return true if successful. False return indicates that
* the actual value was not equal to the expected value.
*/public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/**
* Atomically increment by one the current value.
* @return the previous value
*/public final int getAndIncrement() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return current;
}
}
Здравствуйте, Tiraspol, Вы писали:
T>в какие операции не могут вклиниваться другие потоки.
T>Вот например i++ — потоко безопасная? T>а i = i +1, или i = Item.foo() + i + i; T>Есть ли понятия минимальной потоковой операции?
i++, i = i + 1 и т.п. не являются сами по себе потокобезопасными в том случае, если i -- не локальная переменная. Потому, что подразумевают чтение, изменение, а потом запись. К слову, инструкция iinc может использоваться для изменения (увеличения/уменьшения на 1 или другое число) только локальных переменных. Но локальные переменные потокобезопасны совсем не поэтому -- просто не существует возможности одному потоку доступиться к локальным переменным другого. Для атомарности инкремента и других подобных операций следует пользоваться классами, как уже сказали, из java.util.concurrent.atomic (или по старинке synchronized блоком).
Следует также отметить, что чтение и запись long и double значений (если они не volatile) не является атомарной.
17.7 Non-atomic Treatment of double and long
Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32 bit values. For efficiency's sake, this behavior is implementation specific; Java virtual machines are free to perform writes to long and double values atomically or in two parts.
For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64 bit value from one write, and the second 32 bits from another write. Writes and reads of volatile long and double values are always atomic. Writes to and reads of references are always atomic, regardless of whether they are implemented as 32 or 64 bit values.
VM implementors are encouraged to avoid splitting their 64-bit values where possible. Programmers are encouraged to declare shared 64-bit values as volatile or synchronize their programs correctly to avoid possible complications.
Здравствуйте, boluba, Вы писали:
B>Здравствуйте, dshe, Вы писали:
D>>Верно. D>>В догонку: инструкции процессора могут выполняться неатоматно, что уж говорить о байткодах. D>> <skiped> B>
B>inc mem
B>
D>>уже нет.
B>Правильно ли я понимаю, что без префикса lock команда будет запущена заново после возврата в этот поток исполнения? B>Хотя это скорее для другого форума вопрос.
Не совсем. Префикс lock не играет существенной роли, если машина однопроцессорная, поскольку аппаратные прерывания не могут прервать выполнение инструкции где-то на середине (хотя, возможно, для векторных инструкций есть нюансы). Однако если машина многопроцессорная, то пока один процессор (выполняя инструкцию inc) вычитав данные выполняет арифметическое сложение, другой процессор может вклиниться и тоже обратиться к памяти (считав еще неизмененные данные или записав значение, которое в последствии будет перезаписано). Префикс lock предотвращает такую ситуацию просто блокируя доступ к общей памяти для других процессоров на время выполнения текущей инструкции.
Здравствуйте, Tiraspol, Вы писали:
T>в какие операции не могут вклиниваться другие потоки.
T>Вот например i++ — потоко безопасная? T>а i = i +1, или i = Item.foo() + i + i; T>Есть ли понятия минимальной потоковой операции?
В догонку...
Даже если i++ атомарна то без объявления i с модификатором volatile вся это атомарность может пойти лесом, т.к. другой поток может просто "не увидеть" изменения значения i, т.к. оно изменится только в локальной памяти (work memory) изменившего его потока (а у другого потока "свое" значение i может быть уже загружено в свою локальную память).
За более детальной информацией см. статьи на тему Java Memory Model а также Java Language Specification.
Здравствуйте, Tiraspol, Вы писали:
T>Вот например i++ — потоко безопасная? T>а i = i +1, или i = Item.foo() + i + i; T>Есть ли понятия минимальной потоковой операции?
Все атомарные операции с примитивами — потоко безопасные.
i++;
i = i + 1;
Здравствуйте, aefimov, Вы писали:
A>Все атомарные операции с примитивами — потоко безопасные. A>i++; A>i = i + 1;
Судя по результатам полученым javap первая операция атомарна т.к. преобразуется в единственную инструкцию в байт коде iinc.
А вот вторая операция не атомарна т.к. преобразуется в
1: iload_1
2: iconst_1
3: iadd
4: istore_1
Но вообще я бы не полагался на это, т.к. какой нибудь компилатор может сопримизировать второй вариант к первому или наоборот.
Здравствуйте, aefimov, Вы писали:
T>>Вот например i++ — потоко безопасная? T>>а i = i +1, или i = Item.foo() + i + i; T>>Есть ли понятия минимальной потоковой операции?
A>Все атомарные операции с примитивами — потоко безопасные. A>i++; A>i = i + 1;
настаиваю на не путать атомарность и потокобезопасность!
Здравствуйте, Lucker, Вы писали:
L>настаиваю на не путать атомарность и потокобезопасность!
Я может чего не так понимаю, но я знаю две вещи.
1. Все операции с 32 битными числами в JVM атомарны, если для их вычисления не требуется выполнение какого то метода.
2. JVM гарантирует что атомарная операция — thread-safe.
Я не прав? Тема на самом деле довольно интересная. статей много по этому поводу, как и мнений. Было бы интересно услышать ваше объяснение.
Tiraspol пишет:
> в какие операции не могут вклиниваться другие потоки. > > Вот например i++ — потоко безопасная? > а i = i +1, или i = Item.foo() + i + i; > Есть ли понятия минимальной потоковой операции?
Как я понимаю потоки в Яве реализуются таки не без поддержки ОС.
И значит понятие "могут вклиниться" может быть связано не только
с атомарностью, но и с тем в какой код все это превращается на
кокретном типе процессора.
Например на RISС процессоре одно на Intel другое, на Itanium третье.
--
WBR Денис Цыплаков /* ICQ UIN : 108477017 */
Знающий не говорит, говорящий не знает
Здравствуйте, aefimov, Вы писали:
A>Я может чего не так понимаю, но я знаю две вещи. A>1. Все операции с 32 битными числами в JVM атомарны, если для их вычисления не требуется выполнение какого то метода.
ну это всего лишь значит что при выполнении таких операций в многопоточной среде ты не получишь "неожиданного" результата, как например в случае с long-ами, где старшие байты могут быть изменены в одном потоке, младшие в другом и в результате получится не рыба не мясо.
A>2. JVM гарантирует что атомарная операция — thread-safe.
хм, где это написано? Понятия атомарности и потокобезопасности, как говорят тут люди, перпендикулярны. атомарная операция — логически неделимая операция, в то время как потокобезопасная операция — операция, функционирующая коректно в многопоточной среде. Т.е. я бы даже сказал что понятие потокобезопасности должно применять не к операции а к участку кода, вызывающего эту операцию, так как, конечно, в рамках одного потока такая операция будет отрабатывать корректно, но вот коммулятивно м многопоточной среде...
A>Я не прав? Тема на самом деле довольно интересная. статей много по этому поводу, как и мнений. Было бы интересно услышать ваше объяснение.
Здравствуйте, aefimov, Вы писали:
A>Здравствуйте, Lucker, Вы писали:
L>>настаиваю на не путать атомарность и потокобезопасность!
A>Я может чего не так понимаю, но я знаю две вещи. A>1. Все операции с 32 битными числами в JVM атомарны, если для их вычисления не требуется выполнение какого то метода.
Сама операция "i+1" атомарна (при загруженом значении i), но вот операуия i = i + 1; включает в себя загрузку значения i, сложение, сохранение занчение i. Соответственно даже если каждая их этих операции атомарна, то исполение всех трех операторов явно не атомарно.
A>2. JVM гарантирует что атомарная операция — thread-safe.
Что подразумевается под thread-safe? Если то что другой поток смполнив эту строку (i = i + 1) получит в i значение которое ровно на 1 больше полученного только что другим потоком. То это явно не thread-safe по 2 причинам: операция не атомарная, между этими потоками может не быть memory barier, соответственно каждый поток использует значение i ранее загруженое в его рабочую память.
Денис Цыплаков пишет:
>> Вот например i++ — потоко безопасная?
> Например на RISС процессоре одно на Intel другое, на Itanium третье.
В догонку — больше чем уверен, можно найти процессор, где i++ для
поля как минимум, а может и для локальной переменной — более одной
команды процессора.
Даже хуже того — я почти уверен в том, что говорю.
Переключением потоков занимается никак не Ява — переключение потоков
по идее реализовано на уровне взаимодействия ОС и процессора
доказательство — ОС знает сколько в Яве потоков, значит ОС из
переключает. Если iinc состоит из более одной команды процессора,
достать, увеличить, положить (косвенная даресация не во всех
процессорах хорошо развита), то ОС (точнее сам CPU) никак не может
знать на какой стадии исполнения атомарной Ява операции программа
находится.
--
WBR Денис Цыплаков /* ICQ UIN : 108477017 */
Знающий не говорит, говорящий не знает
Здравствуйте, Денис Цыплаков, Вы писали:
ДЦ>Tiraspol пишет:
>> в какие операции не могут вклиниваться другие потоки. >> >> Вот например i++ — потоко безопасная? >> а i = i +1, или i = Item.foo() + i + i; >> Есть ли понятия минимальной потоковой операции?
ДЦ> Как я понимаю потоки в Яве реализуются таки не без поддержки ОС. ДЦ> И значит понятие "могут вклиниться" может быть связано не только ДЦ> с атомарностью, но и с тем в какой код все это превращается на ДЦ> кокретном типе процессора.
ДЦ> Например на RISС процессоре одно на Intel другое, на Itanium третье.
ну конечно! Ведь задача java — это абстрагирование кода от физической среды выполнения. И если в спеке говорится что операция атомарна — значит атомарнаЮ и пофиг на каком проце она работаетю. Другое дело что реализовывать эту атомарность она может при помощи команд процессора или без нее, что влиять должно только на скорость выполнения кода.
Здравствуйте, Денис Цыплаков, Вы писали:
ДЦ> Как я понимаю потоки в Яве реализуются таки не без поддержки ОС. ДЦ> И значит понятие "могут вклиниться" может быть связано не только ДЦ> с атомарностью, но и с тем в какой код все это превращается на ДЦ> кокретном типе процессора.
ДЦ> Например на RISС процессоре одно на Intel другое, на Itanium третье.
Эээээ... А как же тогда переносимость? Конечно оно да, разные JVM и компиляторы могут исполнять код по-разному в разных ОС. Но существую так сказать "минимальные гарантии", следуя которым ваш код всегда будет исполняться корректно.
Эти гарантии и описываеть Java Memory Model и соответсвующая секция JLS.
P.S. Кстати в Java 5 Java Memory Model было усовершенствованна (не помню какой JSR , т.е. если следовать спецификации многопоточные программы могут исполняться по разному в 1.4 и 5. В прошлой Memory Model вообще были явные баги например с final, volatile и т.д.
Здравствуйте, Денис Цыплаков, Вы писали:
>> Например на RISС процессоре одно на Intel другое, на Itanium третье.
ДЦ> В догонку — больше чем уверен, можно найти процессор, где i++ для ДЦ> поля как минимум, а может и для локальной переменной — более одной ДЦ> команды процессора.
ДЦ> Даже хуже того — я почти уверен в том, что говорю.
И опять, в java не существует понятие процессора, есть только JVM, все что под ней — хз что там.
ДЦ> Переключением потоков занимается никак не Ява — переключение потоков ДЦ> по идее реализовано на уровне взаимодействия ОС и процессора ДЦ> доказательство — ОС знает сколько в Яве потоков, значит ОС из ДЦ> переключает. Если iinc состоит из более одной команды процессора, ДЦ> достать, увеличить, положить (косвенная даресация не во всех ДЦ> процессорах хорошо развита), то ОС (точнее сам CPU) никак не может ДЦ> знать на какой стадии исполнения атомарной Ява операции программа ДЦ> находится.
ну и опять. реализованы потоки в java тоже могут быть как угодно. могут через pthreads, а могут и нет, если потоки, например, не подерждиваются ОС (green-threads) А могут быть варианты, где можно выбрать при запуске как они будут реализовываться. Конечно нативные потоки будет плантировать OS, но это никак не должно отразиться на выполнении кода, и JVM должа позаботиться об атомарности атомарных операций!
Здравствуйте, Andrei N.Sobchuck, Вы писали:
ANS>i++ даже с volatile не потоко безопасна. Кстати, рекоммендую посмотреть на (и пользоваться) java.util.concurrent.
К сожаления на работе до сих пор 1.4
Хотя 5 конечно смотрел но не до таких подробностей.
ANS>Например, в AtomicInteger инкремент реализован так (приведены только важные части класса): ANS>
ANS>public class AtomicInteger extends Number implements java.io.Serializable {
...
ANS> /**
ANS> * Atomically increment by one the current value.
ANS> * @return the previous value
ANS> */
ANS> public final int getAndIncrement() {
ANS> for (;;) {
ANS> int current = get();
ANS> int next = current + 1;
ANS> if (compareAndSet(current, next))
ANS> return current;
ANS> }
ANS> }
ANS>
Lucker пишет:
> ну и опять. реализованы потоки в java тоже могут быть как угодно. могут > через pthreads, а могут и нет, если потоки, например, не подерждиваются > ОС (green-threads) А могут быть варианты, где можно выбрать при запуске > как они будут реализовываться. Конечно нативные потоки будет > плантировать OS, но это никак не должно отразиться на выполнении кода, и > JVM должа позаботиться об атомарности атомарных операций!
Да. Надо было добавить, что я имею ввиду так сказать худший случай.
green-threads так сказать более безопасны, но имеют другие надостатки
--
WBR Денис Цыплаков /* ICQ UIN : 108477017 */
Знающий не говорит, говорящий не знает
korostoff пишет:
> ДЦ> Например на RISС процессоре одно на Intel другое, на Itanium третье. > > Эээээ... А как же тогда переносимость? Конечно оно да, разные JVM и > компиляторы могут исполнять код по-разному в разных ОС. Но существую так > сказать "минимальные гарантии", следуя которым ваш код всегда будет > исполняться корректно.
Ну я это так понимаю, что если программа правильная, то она правильно
работает везде, а если содержит ошибки (связанные с синхронизацией
потоков например), то где-то она может работать, а где то нет. Ява
гантирует платофрмонезависимость для правильных программ.
В догонку есть такой код
public class C
{
public volatile long n1;
}
Делается
instanceOfC.n1++;
Получается в этом случае Ява неявно выполняет довольно
сложные и дорогие акробатические танцы с бубном для
синхронизации потоков? По идее так. Получается volatile
может вести к очень большим накладным расходам.
Надо попробовать тест сделать.
--
WBR Денис Цыплаков /* ICQ UIN : 108477017 */
Знающий не говорит, говорящий не знает
Здравствуйте, Денис Цыплаков, Вы писали:
ДЦ> В догонку есть такой код
ДЦ>
ДЦ>public class C
ДЦ>{
ДЦ> public volatile long n1;
ДЦ>}
ДЦ>
ДЦ> Делается ДЦ>
ДЦ> instanceOfC.n1++;
ДЦ>
ДЦ> Получается в этом случае Ява неявно выполняет довольно ДЦ> сложные и дорогие акробатические танцы с бубном для ДЦ> синхронизации потоков?
Да, точно так. Но как уже сказали http://rsdn.ru/Forum/Message.aspx?mid=1952581&only=1
volatile тут на самом деле поткобезопасность (получение уникального n1 в многопоточной среде не гарантирует).
Вилимо потому что последовательность
загрузка volatile n1 из master memory, инкремент, сохранение нового значения n1 в master memory
само по себе не атомарно
Здравствуйте, korostoff, Вы писали:
K>Даже если i++ атомарна то без объявления i с модификатором volatile вся это атомарность может пойти лесом, т.к. другой поток может просто "не увидеть" изменения значения i, т.к. оно изменится только в локальной памяти (work memory) изменившего его потока (а у другого потока "свое" значение i может быть уже загружено в свою локальную память). K>За более детальной информацией см. статьи на тему Java Memory Model а также Java Language Specification.
Щас наверное меня побьют тут старшие товарисчи, но лучше я буду выглядеть балбесом, чем просто не буду знать, что происходит на самом деле.
Вообщем так:
Нет никакой work memmory. Это скажем так, выдумки (а может и не выдумки, а так у них просто реализовано) IBM. Есть Head Memory или Shared Memory (это то что в написано в спеке), в этой памяти хранятся все не локальные вещи. Ее можно read/write. Причем можно это делать либо в normal либо в non-volatile режимах. Далее, в потоках есть так называемые атомарные действия, причем JVM может сама менять порядок их выполнения. Так вот, для volatile переменных ставится автоматический lock на запись и на чтение, тем самым добиваются влияния на процес перемешивания атомарных действий внутри JVM. То есть volatile, нужно исключительно для того, чтобы за одну операцию получить корректное значение в нескольких переменных, которые могут быть записаны в другом треде/тредах.
private int a = 0;
private int b = 100;
public void inc() {
a++;
b--;
}
public void check() {
if (a == b) {
// ...
}
}
Вот в данном случае нужен volatile по обоим переменным, но только для того, чтобы быть уверенным, что JVM не переставит очередь операций (а их тут три, a++, b-- и a == b).
Т.е. теперь я постараюсь объяснить почему volatile на i++ не нужен и операция является thread-safe, как и остальные атомарные действия.
private int i;
public void thread1() {
int t = i++;
System.out.println("1:" + t);
}
public void thread2() {
int t = i++;
System.out.println("1:" + t);
}
В этом случае невозможно получить output с двумя одинаковыми значениями, volatile тут не нужен.
Я понимаю, что мне сейчас скажут про кеши процессора, если этот код будет выполнять на многопроцессорной тачке. Но, в JLS по этому поводу ничего не сказано.
И еще, нет документа, называющегося Java Memory Model, есть раздел в JLS, о Memory model которая используется для управления состояниями переменных из тредов JVM.
Здравствуйте, aefimov, Вы писали:
A>Здравствуйте, korostoff, Вы писали:
A>Щас наверное меня побьют тут старшие товарисчи, но лучше я буду выглядеть балбесом, чем просто не буду знать, что происходит на самом деле. A>Вообщем так: A>Нет никакой work memmory. Это скажем так, выдумки (а может и не выдумки, а так у них просто реализовано) IBM.
Посмотрел в JLS 3.0 — там нет такого термина. В предыдущей JLS точно был, зуб даю .
A>Я понимаю, что мне сейчас скажут про кеши процессора, если этот код будет выполнять на многопроцессорной тачке. Но, в JLS по этому поводу ничего не сказано.
Опять же, в JLS 3.0 все это написано абстрактно (без ссылок на причины проблем), они ввели новое понятие "Happens Before Ordering" и пляшут от него, в предудущей было написано про кеши и т.д.
Но касательно твоего примера
A>
A>private int i;
A>public void thread1() {
A> int t = i++;
A> System.out.println("1:" + t);
A>}
A>public void thread2() {
A> int t = i++;
A> System.out.println("1:" + t);
A>}
A>
в JLS 3.0 есть такие стороки
When a program contains two conflicting accesses (§17.4.1) that are not ordered
by a happens-before relationship, it is said to contain a data race.
А в примере как раз не соблюдается "happens before" принцип т.к. не выполняется ни одно из условий Happens Before Order (см. 17.4.5) в частности вот это самое интересное
If an action x synchronizes-with a following action y, then we also have hb(x, y).
Соответственно следуя спеке в этом коде data race. И причины его, как решили авторы спеки, не обязательно растокловавасть
Здравствуйте, aefimov, Вы писали:
A>Т.е. теперь я постараюсь объяснить почему volatile на i++ не нужен и операция является thread-safe, как и остальные атомарные действия.
volatile на i++ не нужен, но только потому, что он там не поможет.
A>В этом случае невозможно получить output с двумя одинаковыми значениями, volatile тут не нужен.
Практика — критерий истины. Я запустил твой прогу на пеньке с HT. Прогу несколько модифицировал:
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class ThreadTest {
private final ConcurrentHashMap<Integer, Integer> m = new ConcurrentHashMap<Integer, Integer>();
private int i;
public int thread1() {
return i++;
}
public int thread2() {
return i++;
}
public static void main(String[] args) {
final Random r = new Random();
final ThreadTest t = new ThreadTest();
Thread t1 = new Thread() {
public void run() {
for (; ;) {
int count = t.thread1();
Integer has = t.m.putIfAbsent(count, 1);
if (has != null) System.out.println("1 ("+has+") "+count);
}
}
};
t1.start();
Thread t2 = new Thread() {
public void run() {
for (; ;) {
t.thread2();
int count = t.thread1();
Integer has = t.m.putIfAbsent(count, 2);
if (has != null) System.out.println("2 ("+has+") "+count);
}
}
};
t2.start();
try {
t1.join();
t2.join();
}
catch (InterruptedException e) {
e.printStackTrace();
}
}
}
То есть она запоминает каждое возвращаемое значение. И если уже такое есть, то его выводит. Вот результат без volatile:
Здравствуйте, dshe, Вы писали:
D>Думаю, что неатомарность обычного инкремента может доказать следующий эксперимент.
Нужно доказать, что атомарное действие может быть не thread-safe
D>Давайте попробуем из нескольких потоков вызывать инкремент shared переменной. Если инкремент атомарен, то эта переменная увеличится на число равное количеству потоков умноженное на количество раз, на которое каждый поток попытался ее увеличить. Если инкремент неатомарен, то возможны "пропуски" и переменная увеличится на меньшее число. D>У меня получилось (как и ожидалось): D>
Ваш пример у меня отрабатывает ровно, но при увеличении числа тредов начинает действительно показывать разные результаты. Я ниже объясню почему так происходит, с моей точки зрения.
Все-же я хочу сказать, что любая операция, атомарная, всегда безопасна, но как доказать это на примере я не знаю. Я подразумеваю под thread-safe следующее:
int a = b++;
Есть три состояния, "до выполнения", "выполнение" и "после выполнения". Я утверждаю, что при том же инкременте сценарий такой:
{не thread-safe} добрались до инкремента
{thread-safe!} -> до выполнения: at = a, bt = b
{thread-safe!} -> выполение: at = bt + 1
{thread-safe!} -> после выполения: a = at, b = bt
{не thread-safe} выбрались из инкремента, вот тут значение a уже может быть каким угодно (даже если a объявлена как volatile)
Еще пример:
int c = a + b;
int d = a + b;
Тут две операции и обе атомарны. Когда вычисляется c — я уверен (JVM это гарантирует), что в момент вычисления ни a ни b не изменятся. Т.е. если в операцию пришло 2 и 3, то значит c будет 5. Но, при этом же, после выполнения c = a + b, JVM может переключится на другую операцию, вместо того чтобы вычислить d = a + b (допустим, что JVM не догадалось использовать результат . И поэтому, в момент вычисления d = a + b, сами a и b могут уже быть другими. В результате возможна ситуация, когда c != d. И все это, только лишь потому, что тут не одна атомарная операция, а две.
Вернемся к примерам, дело в том, что это не совсем "честный" тест. Особенно, что касается циклов. JVM просто соптимизировала их. Суть теста должна заключаться в том, чтобы столкнуть треды на этом инкременте, цикл для этого не подходит. Надо каждый раз создавать пучок тредов и пускать их одновременно (notifyAll). Ниже измененный тест, он отрабатывает не мгновенно, как ваш, т.е. тут JVM уже не смогла ничего оптимизировать.
public class Main implements Runnable {
private static final int ITERATIONS = 1000;
private static final int THREADS = 100;
private/*volatile*/int counter = 0;
public static void main(String[] args) {
Main main = new Main();
Thread[] threads = new Thread[THREADS];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(main);
threads[i].start();
}
for (int i = 0; i < ITERATIONS; i++) {
int old = main.counter;
// Wait until all threads deadfor (Thread thread = getActiveThread(threads); thread != null; thread = getActiveThread(threads)) {
synchronized (main) {
// Release waiting threads
main.notifyAll();
}
}
// Start again (keep waiting)if (i < ITERATIONS - 1) {
for (int j = 0; j < threads.length; j++) {
threads[j] = new Thread(main);
threads[j].start();
}
}
if (old + THREADS != main.counter) {
System.out.println("fault on iteration " + i + ": " + (main.counter - old) + " increments missed");
}
}
System.out.println("actual : " + main.counter);
System.out.println("expected : " + (ITERATIONS * THREADS));
System.out.println("passed : " + (main.counter == (ITERATIONS * THREADS)));
}
private static Thread getActiveThread(Thread[] threads) {
for (int i = 0; i < threads.length; i++) {
Thread thread = threads[i];
if (thread.isAlive()) {
return thread;
}
}
return null;
}
public void run() {
freeze();
counter++;
}
private synchronized void freeze() {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Также и с другим примером, который привели с AtomicInteger — там идет вызов к функции, в недрах Unsafe, и JVM не может такое оптимизировать. Поэтому, я грешу на то, что тесты показывают такие результаты именно из-за оптимизатора. Но я как-то задал 1000 тредов на 1000 итераций в этом тесте и после десяти минут ожидания оно таки выдало, что "fault on iteration 378: 177 increments missed", правда при этом у меня комп просто умер и IDE долго не откликалось, возможно — это наведенные помехи
Здравствуйте, dshe, Вы писали:
D>Думаю, что неатомарность обычного инкремента может доказать следующий эксперимент. D>Давайте попробуем из нескольких потоков вызывать инкремент shared переменной. Если инкремент атомарен, то эта переменная увеличится на число равное количеству потоков умноженное на количество раз, на которое каждый поток попытался ее увеличить. Если инкремент неатомарен, то возможны "пропуски" и переменная увеличится на меньшее число.
Извиняюсь, вчерашний пример был с ошибкой. Там двойная синхронизация была, в результате отпускался только один трет с каждым notifyAll, т.е. data race не было. Вот это правильный пример, тут они сталкиваются лбами
public class Main implements Runnable {
private static final int ITERATIONS = 100;
private static final int THREADS = 100;
private/*volatile*/int counter = 0;
private boolean keepWaiting = true;
public static void main(String[] args) {
Main main = new Main();
Thread[] threads = new Thread[THREADS];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(main);
threads[i].start();
}
for (int i = 0; i < ITERATIONS; i++) {
int old = main.counter;
synchronized (main) {
// Release waiting threads
main.keepWaiting = false;
main.notifyAll();
}
// Wait until all threads deadfor (Thread thread = getActiveThread(threads); thread != null; thread = getActiveThread(threads)) {
synchronized (main) {
try {
System.out.println("waiting threads on iteration " + i + ": active threads - " + Thread.activeCount());
main.wait(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized (main) {
main.keepWaiting = true;
// Start again (keep waiting)if (i < ITERATIONS - 1) {
for (int j = 0; j < threads.length; j++) {
threads[j] = new Thread(main);
threads[j].start();
}
}
}
if (old + THREADS != main.counter) {
System.out.println("fault on iteration " + i + ": " + (main.counter - old) + " increments missed");
}
}
System.out.println("actual : " + main.counter);
System.out.println("expected : " + (ITERATIONS * THREADS));
System.out.println("passed : " + (main.counter == (ITERATIONS * THREADS)));
}
private static Thread getActiveThread(Thread[] threads) {
for (int i = 0; i < threads.length; i++) {
Thread thread = threads[i];
if (thread.isAlive()) {
return thread;
}
}
return null;
}
public void run() {
synchronized (this) {
try {
if (keepWaiting) {
wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
counter++;
}
}
Здравствуйте, Andrei N.Sobchuck, Вы писали:
ANS>Declaring a field as volatile differs only in that no locking is involved. In particular, composite read/write operations such as the "++'' operation on volatile variables are not performed atomically. ANS>
Спасибо, почитаю. То, что инкремент не атомарен, это конечно — открытие для меня...
ANS>Кстати, твой пример (второй), естественно, тоже не проходит. Если он у тебя проходит, то вполне возможно, что там просто однопроцесорная машина (и без HT).
Угу, без HT. На HT оно начинает бодаться? Проверить бы еще на полноценных двух-бошковых...
Здравствуйте, aefimov, Вы писали:
ANS>>Кстати, твой пример (второй), естественно, тоже не проходит. Если он у тебя проходит, то вполне возможно, что там просто однопроцесорная машина (и без HT).
A>Угу, без HT. На HT оно начинает бодаться? Проверить бы еще на полноценных двух-бошковых...
Тоже н епроходит Проверил толи на 4-х головом пеньке, толи на 2-х головом но с HT.
Я так понимаю, что на однопроцесорной машине всё таки работа идёт последовательно, и вероятность, что тред остановится именно посередине инкремента небольшая. Плюс, если не будет volatile, то значения будут кешироваться в кешах отдельных процесоров, и тут даже атомарность int не поможет.
Спасибо!
Всетаки атомарность определяется на уровне инструкций байт кода, или же это вообще отдельные вещи?
Т.е. чтоже конкретно можно считать атомарным?
Re[2]: Не делимые операции потоков
От:
Аноним
Дата:
15.06.06 06:58
Оценка:
Здравствуйте, boluba, Вы писали:
B>Подводя черту под volatile: все, что они могут делать это заменить один тип рассинхронизации при работе нескольких процессов на другой . Какой в этом смысл и к что на самом деле хотели сделать — я не знаю. Вот с этим и живу .
Бытует мнение что volatile необходим для того чтобы поток гарантированно "увидел" изменение переменной (из другого потока). То есть якобы в многопроцессорной среде один поток может изменить переменную, а второй поток может бесконечно долго продолжать работать с working copy и "не заметить" ее изменение.
Правда ли это?
Здравствуйте, Аноним, Вы писали:
А>Бытует мнение что volatile необходим для того чтобы поток гарантированно "увидел" изменение переменной (из другого потока). То есть якобы в многопроцессорной среде один поток может изменить переменную, а второй поток может бесконечно долго продолжать работать с working copy и "не заметить" ее изменение. А>Правда ли это?
Да, если переменная не volatile или если между запись в одном потоке/чтение в другом не было memory barrier (т.е. если после записи не освобожден монитор, а перед чтением не получен ТОТЖЕ монитор).
Что особенно интересно в старой memory model (читай глава Threading JLS) при освобожении монитра в master memory флашаться (записываются) ВСЕ иземененные переменные, а при захвате монитора "сбрасывается" (reset) ВСЯ working memory потока. В новой же memory model (JLS 3.0) вообще убраны понятия working/master memory. Там введено понятие "happens before ordering" и в соотвтетсвии с ним JVM иммет право при освобожедении монитора не сбрасывать измененные переменные в master memory если JVM точно уверен что эта измененная переменная не будет читаться другим потоком захвативжим ТОТ ЖЕ монитор.
Т.е. если раньше можно было бы написать так (хотя делать так не советую)
synchronized (new Object) {
} // memory barrierint resA = this.a
int resB = this.b;
То теперь следуя спецификации этот код не верен, т.к. JVM не обязана обеспечивать видимость изменений shared переменных если операции "запись в потоке1"/"чтение в потоке2" не следуют happens-before-ordering (а они не следуют если не между этими операциями нет синхронизации на одном и том же объекте или записи/чтения одной и той же volatile переменной)
Здравствуйте, Tiraspol, Вы писали:
T>в какие операции не могут вклиниваться другие потоки.
T>Вот например i++ — потоко безопасная? T>а i = i +1, или i = Item.foo() + i + i; T>Есть ли понятия минимальной потоковой операции?
T>Спасибо.
Есть интересная главка нумер 17 в Java Language Specification.
Вообще ее всю конечно нехило прочитать, но вот хотя-бы абзац:
17.7 Non-atomic Treatment of double and long
Some implementations may find it convenient to divide a single write action
on a 64-bit long or double value into two write actions on adjacent 32 bit values.
For efficiency's sake, this behavior is implementation specific; Java virtual
machines are free to perform writes to long and double values atomically or in two
parts.
For the purposes of the Java programming language memory model, a single
write to a non-volatile long or double value is treated as two separate writes: one
to each 32-bit half. This can result in a situation where a thread sees the first 32
bits of a 64 bit value from one write, and the second 32 bits from another write.
Writes and reads of volatile long and double values are always atomic. Writes to
and reads of references are always atomic, regardless of whether they are implemented
as 32 or 64 bit values.
VM implementors are encouraged to avoid splitting their 64-bit values where
possible. Programmers are encouraged to declare shared 64-bit values as volatile
or synchronize their programs correctly to avoid possible complications.
Re[3]: Не делимые операции потоков
От:
Аноним
Дата:
15.06.06 13:50
Оценка:
Здравствуйте, aefimov, Вы писали:
A>Спасибо! A>Всетаки атомарность определяется на уровне инструкций байт кода, или же это вообще отдельные вещи? A>Т.е. чтоже конкретно можно считать атомарным?
Я верю в спецификации . Ни в JLS, ни в JVMSpec нет ничего относительно переключения потоков и связанных с этим гарантий. Это сделано намерено, т.к. это дает большую свободу при реализации. Хочешь, переложи все на ОС, нет возможности, делай управление потоками самостоятельно.
Как я уже писал, JLS объявляет атомарными "странные" метаоперации. Они не входят в состав языка Java, но это и не байт коды. Застряли где-то посередине. Это как бы описание действий JVM, которые должны быть гарантированно атомарными. А для этого, очевидно, в каждой JVM подобные метаоперации должны быть выделимы логически. Цель создателей JLS ясна — наложить как можно меньше ограничений на реализацию и дать как можно больше гарантий времени выполнения. О результатах можно спорить.
Мое мнение:
— исполнение одного байткода (любого) нельзя считать атомарным.
— это следовало бы гарантировать, хотя всех последствий я оценить не могу.
CHAPTER 3 INSTRUCTION SET REFERENCE, A-M
. . . INC—Increment by 1
Adds 1 to the destination operand, while preserving the state of the CF flag. The destination
operand can be a register or a memory location. . . .
This instruction can be used with a LOCK prefix to allow the instruction to be executed atomically.
т.е.
lock inc mem
выполняется атомарно, а
inc mem
уже нет.
--
Дмитро
Re[4]: Не делимые операции потоков
От:
Аноним
Дата:
15.06.06 15:28
Оценка:
Здравствуйте, korostoff, Вы писали:
K>Да, если переменная не volatile или если между запись в одном потоке/чтение в другом не было memory barrier (т.е. если после записи не освобожден монитор, а перед чтением не получен ТОТЖЕ монитор).
Тут я вас не понял. Почему именно "тотже" монитор? Поясните пожалуйста
K>Что особенно интересно в старой memory model (читай глава Threading JLS) при освобожении монитра в master memory флашаться (записываются) ВСЕ иземененные переменные, а при захвате монитора "сбрасывается" (reset) ВСЯ working memory потока. В новой же memory model (JLS 3.0) вообще убраны понятия working/master memory. Там введено понятие "happens before ordering" и в соотвтетсвии с ним JVM иммет право при освобожедении монитора не сбрасывать измененные переменные в master memory если JVM точно уверен что эта измененная переменная не будет читаться другим потоком захвативжим ТОТ ЖЕ монитор.
Вот здесь тоже есть разногласия в понимании JLS . Вы пишите "ВСЕ иземененные переменные", а есть мнение, что только те, к которым относиться замок. Иными словами, освобождения замка объекта приводит к записи данных объекта в общую память. А если процесс менял данные других объектов?
K>Т.е. если раньше можно было бы написать так (хотя делать так не советую)
K>поток1: K>
K>То теперь следуя спецификации этот код не верен, т.к. JVM не обязана обеспечивать видимость изменений shared переменных если операции "запись в потоке1"/"чтение в потоке2" не следуют happens-before-ordering (а они не следуют если не между этими операциями нет синхронизации на одном и том же объекте или записи/чтения одной и той же volatile переменной)
Здравствуйте, dshe, Вы писали:
D>Верно. D>В догонку: инструкции процессора могут выполняться неатоматно, что уж говорить о байткодах. D> <skiped>
inc mem
D>уже нет.
Правильно ли я понимаю, что без префикса lock команда будет запущена заново после возврата в этот поток исполнения?
Хотя это скорее для другого форума вопрос.