Здравствуйте, 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
Но вообще я бы не полагался на это, т.к. какой нибудь компилатор может сопримизировать второй вариант к первому или наоборот.
Здравствуйте, 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.
Здравствуйте, 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 — это абстрагирование кода от физической среды выполнения. И если в спеке говорится что операция атомарна — значит атомарнаЮ и пофиг на каком проце она работаетю. Другое дело что реализовывать эту атомарность она может при помощи команд процессора или без нее, что влиять должно только на скорость выполнения кода.
Здравствуйте, 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;
}
}
Здравствуйте, Денис Цыплаков, Вы писали:
ДЦ> Как я понимаю потоки в Яве реализуются таки не без поддержки ОС. ДЦ> И значит понятие "могут вклиниться" может быть связано не только ДЦ> с атомарностью, но и с тем в какой код все это превращается на ДЦ> кокретном типе процессора.
ДЦ> Например на 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 */
Знающий не говорит, говорящий не знает
Здравствуйте, 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.
Здравствуйте, Денис Цыплаков, Вы писали:
ДЦ> В догонку есть такой код
ДЦ>
ДЦ>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.