Здравствуйте, ., Вы писали:
PD>>Будет сумма корректной или нет ? .>Да тут вообще многопоточного доступа нет. Тред запускается только после того, как всё запишется. Интуитивно думаю, что запуск треда гарантирует happens before, хотя ссылку на спеку не дам, искать лень.
Тут обсуждение частично подтверждает мою интуицию: http://stackoverflow.com/questions/7651226/java-happend-before-thread-start
Хотя там говорится об ExecutorService, мне кажется что и голые треды работают так же.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, Pavel Dvorkin, Вы писали: PD>Хорошо, тогда запустим тред заранее
Вот тогда да, нужна синхронизация или volatile. С синхронизацией примерно всё понятно, я полагаю, а с volatile можно делать так:
class A
{
volatile int[] a;
int s;
void main()
{
Thread2.start();
f();
Thread2.join();
}
void f() // вызывается из mainThread
{
int []a = new int[1000];
for (int i = 0; i < 1000; i++)
a[i] = i;
this.a = a;
}
int sum() // вызывается из Thread2
{
Thread.sleep(10000);
for (int i = 0; i < 1000; i++)
s+=i;
}
Ещё есть AtomicIntegerArray.
А sleep(10000) ничего не гарантирует и для C++, если что-то где-то не успеет, засвопит что-нибудь со страшной силой или в hibernate что-нибудь отправиться, вот и всё — неуловимый баг.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
PD>Пусть 100% гарантированно read выполняется после write. synchronized тогда смысла не имеет — в этот монитор никто войти и не попытается, пока он занят. Но мне же StanislavK доказывал, что у потока есть своя memory (ну ладно, деталей этого я не знаю, а вот что уж точно знаю, это то, что у ядра есть кэш.). Предположим, что поток write записал в элементы массива с нулевого по 999 (это ему кажется, что он в RAM записал, а попало только в кеш или эту Java working memory). Поток read читает через пару минут и ? Что он прочитает ? Или сам факт входа в synchronized работает как flush all thread memory ?
synchronized здесь в том числе и для обеспечения эффекта memory consistency. Т.е. гарантирует что поток B увидит последние данные записанные в массив потоком A (при условии что записаны они были внутри synchronized блока на том же мониторе) а не кешированную копию. Упрощенно говоря можно назвать это и flush thread memory, но это справедливо не всего кеша потока а только для того состояния/переменных/объектов которые были записаны в аналогичном synchronized блоке в другом потоке (в Java говорят что существует отношение happens-before между действиями совершенными до выхода из synchronized и действиями которые будут совершены при последующем входе в synchronized на том же мониторе)
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
Здравствуйте, ., Вы писали:
.>Здравствуйте, Pavel Dvorkin, Вы писали: PD>>Хорошо, тогда запустим тред заранее .>Вот тогда да, нужна синхронизация или volatile. С синхронизацией примерно всё понятно, я полагаю, а с volatile можно делать так:
Нет, все равно неясно.
volatile гарантирует, что this.a будет атомарно равно a из int []a = new int[1000]; Это хорошо, но вопрос не об этом. Еще раз посмотри мой пример. Ссылку на массив я там не меняю вообще, поэтому о ее валидности заботиться незачем. Но я меняю данные в массиве. Если уж volatile, то надо объявить volatile все элементы массива. . А вдруг первый поток записал в a[10], а эти данные остались в кеше ядра первого потока и не попали в RAM ? А если для a[10] попали, то это не гарантирует. что попали для a[100].
И синхронизация по ссылке с помощью лока там тоже ни к чему по той же причине : я не меняю ссылку, а меняю данные по ссылке. Лочить надо, стало быть, все элементы массива
.>>Вот тогда да, нужна синхронизация или volatile. С синхронизацией примерно всё понятно, я полагаю, а с volatile можно делать так:
PD>Нет, все равно неясно.
PD>volatile гарантирует, что this.a будет атомарно равно a из int []a = new int[1000]; Это хорошо, но вопрос не об этом. Еще раз посмотри мой пример. Ссылку на массив я там не меняю вообще, поэтому о ее валидности заботиться незачем. Но я меняю данные в массиве. Если уж volatile, то надо объявить volatile все элементы массива. . А вдруг первый поток записал в a[10], а эти данные остались в кеше ядра первого потока и не попали в RAM ? А если для a[10] попали, то это не гарантирует. что попали для a[100].
PD>И синхронизация по ссылке с помощью лока там тоже ни к чему по той же причине : я не меняю ссылку, а меняю данные по ссылке. Лочить надо, стало быть, все элементы массива
Не только атомарно (хотя на атомарность оно не влияет, т.к. это reference, который и так атомарен), а с гарантией happens-before: т.е. действия (изменение эл-тов массива в твоём случае) совершенные до this.a=a будут выгядеть happens-before для всех потоков потоков.
Ту же гарантию дают и локи, но они сильнее влияют на производительность.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, E.K., Вы писали:
EK>Здравствуйте, Pavel Dvorkin, Вы писали:
PD>>Пусть 100% гарантированно read выполняется после write. synchronized тогда смысла не имеет — в этот монитор никто войти и не попытается, пока он занят. Но мне же StanislavK доказывал, что у потока есть своя memory (ну ладно, деталей этого я не знаю, а вот что уж точно знаю, это то, что у ядра есть кэш.). Предположим, что поток write записал в элементы массива с нулевого по 999 (это ему кажется, что он в RAM записал, а попало только в кеш или эту Java working memory). Поток read читает через пару минут и ? Что он прочитает ? Или сам факт входа в synchronized работает как flush all thread memory ?
EK>synchronized здесь в том числе и для обеспечения эффекта memory consistency. Т.е. гарантирует что поток B увидит последние данные записанные в массив потоком A (при условии что записаны они были внутри synchronized блока на том же мониторе) а не кешированную копию. Упрощенно говоря можно назвать это и flush thread memory, но это справедливо не всего кеша потока а только для того состояния/переменных/объектов которые были записаны в аналогичном synchronized блоке в другом потоке (в Java говорят что существует отношение happens-before между действиями совершенными до выхода из synchronized и действиями которые будут совершены при последующем входе в synchronized на том же мониторе)
Следует ли из этого, что volatile работает таким же образом, то есть изменение volatile переменной (любой!!!) приводит к записи всех данных из кеша в RAM ?
Иначе получится, что при замене volatile ссылки на другую мы можем по этой новой ссылке иметь данные, созданные другим потоком и находящиеся в его кеше, а не в RAM ?
Опять же, какого потока ? Переменная-то потоку не принадлежит, откуда мне знать, в каком потоке ее сделали ?
И опять же, как это согласуется с тем, что , как утверждает Sun/Oracle, замена ссылки всегда атомарна , даже без volatile ? Получается что атомарна-то она атомарна, да только без volatile может привести к тому, что новая ссылка показывает на данные еще не в RAM для поменявшего ссылку потока.
PD>>И синхронизация по ссылке с помощью лока там тоже ни к чему по той же причине : я не меняю ссылку, а меняю данные по ссылке. Лочить надо, стало быть, все элементы массива .>Не только атомарно (хотя на атомарность оно не влияет, т.к. это reference, который и так атомарен), а с гарантией happens-before: т.е. действия (изменение эл-тов массива в твоём случае) совершенные до this.a=a будут выгядеть happens-before для всех потоков потоков.
Да нет там у меня this.a =a! Там всего одна ссылка, и она не меняется. А вот по ней в элементы массива пишет один поток, а читает через 10 секунд другой.
Так что, надо лочить все элементы массива ?
PD>Нет, все равно неясно.
PD>volatile гарантирует, что this.a будет атомарно равно a из int []a = new int[1000]; Это хорошо, но вопрос не об этом. Еще раз посмотри мой пример. Ссылку на массив я там не меняю вообще, поэтому о ее валидности заботиться незачем. Но я меняю данные в массиве. Если уж volatile, то надо объявить volatile все элементы массива. . А вдруг первый поток записал в a[10], а эти данные остались в кеше ядра первого потока и не попали в RAM ? А если для a[10] попали, то это не гарантирует. что попали для a[100].
Вот здесь вы упускаете один важный момент (см. выделенное).
volatile не только гарантирует то, что поток B увидит последнюю (up-to-date) ссылку записанную в переменную.
Благодаря семантике happens-before volatile так же гарантирует что те действия который были совершены в mainThread до записи ссылки в volatile переменную, будут также видны другим потокам, как только они прочитают значение этой volatile переменной (в нашем случае — Thread2). Т.е. вы можете быть уверены что Thread2 увидит корректные (up-to-date) данные во всем массиве int[]a — не только корректную ссылку a!
Другими словами вам не нежно объявлять каждый элемент массива volatile.
Тут еще конечно нужно понимать что трюк с volatile пройдет только в том случае, если вы можете позволить себе сгенерировать весь массив заранее и потом записать одним махом, перед тем как прочитать в другом потоке.
Если же вам нужно постоянно писать/читать данные в одном и том же массиве по ходу выполнения программы, тогда нужен synchronized, пример которого я привел в соседней ветке. И нужен в первую очередь именно для того чтобы обеспечить memory consistency.
PD>И синхронизация по ссылке с помощью лока там тоже ни к чему по той же причине : я не меняю ссылку, а меняю данные по ссылке. Лочить надо, стало быть, все элементы массива
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
PD>Следует ли из этого, что volatile работает таким же образом, то есть изменение volatile переменной (любой!!!) приводит к записи всех данных из кеша в RAM ?
Не всех данных, а только тех, которые были изменены некими действиями совершенными в потоке до записи volatile переменной (и видны только тем потокам которые впоследствии из этой volatile переменной прочитали)!
PD>Иначе получится, что при замене volatile ссылки на другую мы можем по этой новой ссылке иметь данные, созданные другим потоком и находящиеся в его кеше, а не в RAM ?
Вот именно есть гарантия что увидим то что надо, а не кеш.
PD>Опять же, какого потока ? Переменная-то потоку не принадлежит, откуда мне знать, в каком потоке ее сделали ?
PD>И опять же, как это согласуется с тем, что , как утверждает Sun/Oracle, замена ссылки всегда атомарна , даже без volatile ? Получается что атомарна-то она атомарна, да только без volatile может привести к тому, что новая ссылка показывает на данные еще не в RAM для поменявшего ссылку потока.
Вообще не понял что имеется ввиду.
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Да нет там у меня this.a =a! Там всего одна ссылка, и она не меняется. А вот по ней в элементы массива пишет один поток, а читает через 10 секунд другой. PD>Так что, надо лочить все элементы массива ?
Конечно надо. Ровно так же как и в С++. И количество секунд тут ничего не гарантирует.
Имеется в виду не каждый элемент лочить отдельно, а достаточно залочить некий лок, который общий для всего массива (как показал E.K.).
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, E.K., Вы писали:
EK>Здравствуйте, StanislavK, Вы писали:
SK>>volatile гарантирует 3 вещи:
SK>>3. atomicity для примитивных типов и ссылок. Надо заметить, что проблема может возникнуть _только_ для 64-х битных типов на 32-х битных процессорах, что сейчас большая редкость. Раскрывать тут особо нечего.
EK>Кроме того, volatile расширяет атомарность записи и чтения на long и double.
Согласен. Я просто коряво выразился. Имел ввиду, что при volatile всегда соблюдается atomicity, независимо от типа.
Здравствуйте, E.K., Вы писали:
PD>>volatile гарантирует, что this.a будет атомарно равно a из int []a = new int[1000]; Это хорошо, но вопрос не об этом. Еще раз посмотри мой пример. Ссылку на массив я там не меняю вообще, поэтому о ее валидности заботиться незачем. Но я меняю данные в массиве. Если уж volatile, то надо объявить volatile все элементы массива. . А вдруг первый поток записал в a[10], а эти данные остались в кеше ядра первого потока и не попали в RAM ? А если для a[10] попали, то это не гарантирует. что попали для a[100].
EK>Вот здесь вы упускаете один важный момент (см. выделенное).
EK>volatile не только гарантирует то, что поток B увидит последнюю (up-to-date) ссылку записанную в переменную.
Еще раз объясняю — не меняется в моем примере ссылка!!! Данные по ссылке меняются, а сама ссылка нет. Ссылка 4 байта, данных по ней много, целый граф из множества классов там может висеть.
EK>Благодаря семантике happens-before volatile так же гарантирует что те действия который были совершены в mainThread до записи ссылки в volatile переменную, будут также видны другим потокам, как только они прочитают значение этой volatile переменной (в нашем случае — Thread2). Т.е. вы можете быть уверены что Thread2 увидит корректные (up-to-date) данные во всем массиве int[]a — не только корректную ссылку a!
А вот это обоснуйте, пожалуйста. На вот этом примере
Ставить volatile там незачем и некому — ни одна ссылка там не меняется вообще.
А второе ваше утверждение мне совсем не очевидно. По этой ссылке могут быть мегабайты информации. Они все будут сброшены в RAM ? Когда ? При обращении к volatile ссылке (менять-то ее никто не меняет) ?
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Здравствуйте, StanislavK, Вы писали:
SK>>Так, что вот.. многопоточность, она на самом деле много хуже, чем кажется и читайте Java Memory Model и JVM spec про потоки, там еще много чего написано. PD>Да, наверное ты прав. Я с потоками имел дело не раз на С++. Там нет необходимости учитывать этот memory barrier, так как примитивы синхронизации Win API обеспечивают запись измененных данных в RAM. Но там нет никакой working memory у потока (хотя кэш процессора, конечно, есть). Если в Яве ввели еще некую локальную память для потока, то понятно, что необходимо обеспечить ее синхронизацию с основной памятью.
Там все точно так же. Только семантика реализована на уровне API. В java она, большей частью ( ), на уровне языка.
PD>Для уточнения — такой вопрос. Пусть я в одном потоке записал элементы некоего 100 Кб массива. Что я должен сделать, чтобы другой поток гарантированно увидел эти данные ?
Общее правило такое — изменения флешатся на записи в volatile и на освобождении лока.
Здравствуйте, E.K., Вы писали:
EK>Здравствуйте, Pavel Dvorkin, Вы писали:
PD>>Следует ли из этого, что volatile работает таким же образом, то есть изменение volatile переменной (любой!!!) приводит к записи всех данных из кеша в RAM ?
EK>Не всех данных, а только тех, которые были изменены некими действиями совершенными в потоке до записи volatile переменной (и видны только тем потокам которые впоследствии из этой volatile переменной прочитали)!
То есть при обращении к volatile переменной сбрасываются все изменения в кэше всех потоков ? При любом обращении ?
PD>>Иначе получится, что при замене volatile ссылки на другую мы можем по этой новой ссылке иметь данные, созданные другим потоком и находящиеся в его кеше, а не в RAM ?
EK>Вот именно есть гарантия что увидим то что надо, а не кеш.
Аргументируйте. А если эта ссылка косвенно ссылается на 200 Мб всяких данных ?
PD>>Опять же, какого потока ? Переменная-то потоку не принадлежит, откуда мне знать, в каком потоке ее сделали ?
PD>>И опять же, как это согласуется с тем, что , как утверждает Sun/Oracle, замена ссылки всегда атомарна , даже без volatile ? Получается что атомарна-то она атомарна, да только без volatile может привести к тому, что новая ссылка показывает на данные еще не в RAM для поменявшего ссылку потока.
EK>Вообще не понял что имеется ввиду.
Имеется в виду, что замена ссылки всегда атомарна, как утверждает Sun. Но если она не volatile, то это просто замена 4(8) байт. А если volatile, то (по-вашему) еще и сброс всего кеша всех тредов. В это я как-то плохо верю.
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Еще раз объясняю — не меняется в моем примере ссылка!!! Данные по ссылке меняются, а сама ссылка нет. Ссылка 4 байта, данных по ней много, целый граф из множества классов там может висеть.
Ну не меняется и отлично. Значит используйте synchronized. Я лишь пытался объяснить что volatile это не только memory consistency но и happens-before. Судя по вашим предыдущим комментариям вы этот момент упускаете.
PD>Ставить volatile там незачем и некому — ни одна ссылка там не меняется вообще.
Не понял что еще нужно обосновывать. Семантика volatile и syncronized расписана в деталях в JLS и уже неоднократна объяснялась в этом треде с приведением ссылок на документацию.
Если говорить конкретно о вашем примере — он неверен, поскольку происходит изменение общего состояния при отсутствии какой-либо синхронизации. Вам нужен synchronized, как я уже показывал здесь
Здравствуйте, Pavel Dvorkin, Вы писали:
PD>Имеется в виду, что замена ссылки всегда атомарна, как утверждает Sun. Но если она не volatile, то это просто замена 4(8) байт. А если volatile, то (по-вашему) еще и сброс всего кеша всех тредов. В это я как-то плохо верю.
Не всех, а тех, которые обращаются к этой volatile переменной в данный момент. И не всего кеша, а тот который должен happens-before обращения к этой переменной.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, ., Вы писали:
PD>>Так что, надо лочить все элементы массива ? .>Конечно надо. Ровно так же как и в С++. И количество секунд тут ничего не гарантирует.
С++ тут ни при чем. С таким же успехом можно писать на ассемблере, где локов вообще нет, или на Delphi, где их тоже нет. Как и в С++.
А в Win API локов на участки памяти тоже нет. Есть критическая секция и мютекс, но они понятия не имеют, что они защищают, это нигде в коде не указывается. Они просто синхронизируют входы в участок кода, и при этом выполняют сброс кеша ядра. Поэтому Ява и кто угодно не может по крайней мере в Windows лочить память — не может она делать то, что не позволяет подлежащая платформа.
.>Имеется в виду не каждый элемент лочить отдельно, а достаточно залочить некий лок, который общий для всего массива (как показал E.K.).
То есть ты утверждаешь что при любом обращении к synchronized переменной вся паять всех кешей сбрасывается в RAM ? Иного объяснения я не вижу. Знать, какой поток изменил данные по этой synchronized ссылке никто не может.
EK>>Не всех данных, а только тех, которые были изменены некими действиями совершенными в потоке до записи volatile переменной (и видны только тем потокам которые впоследствии из этой volatile переменной прочитали)!
PD>То есть при обращении к volatile переменной сбрасываются все изменения в кэше всех потоков ? При любом обращении ?
Только в том потоке в котором вы читаете. И только те изменения которые были сделаны до записи volatile.
PD>Аргументируйте. А если эта ссылка косвенно ссылается на 200 Мб всяких данных ?
Значит все 200МБ будут видимы вашему потоку. Конкретная реализация JVM может вообще решить не использовать никакого кеша потока для этих данных — кто его знает? Главное — то, что вам гарантирует спецификация языка.
EK>>Вообще не понял что имеется ввиду.
PD>Имеется в виду, что замена ссылки всегда атомарна, как утверждает Sun. Но если она не volatile, то это просто замена 4(8) байт. А если volatile, то (по-вашему) еще и сброс всего кеша всех тредов. В это я как-то плохо верю.
Не всего кеша и не всех потоков. Не выкручивайте ответы наизнанку.
Я бы посоветовал еще посмотреть первый ответ здесь:
Здравствуйте, StanislavK, Вы писали:
SK>>>Так, что вот.. многопоточность, она на самом деле много хуже, чем кажется и читайте Java Memory Model и JVM spec про потоки, там еще много чего написано. PD>>Да, наверное ты прав. Я с потоками имел дело не раз на С++. Там нет необходимости учитывать этот memory barrier, так как примитивы синхронизации Win API обеспечивают запись измененных данных в RAM. Но там нет никакой working memory у потока (хотя кэш процессора, конечно, есть). Если в Яве ввели еще некую локальную память для потока, то понятно, что необходимо обеспечить ее синхронизацию с основной памятью. SK>Там все точно так же. Только семантика реализована на уровне API. В java она, большей частью ( ), на уровне языка.
PD>>Для уточнения — такой вопрос. Пусть я в одном потоке записал элементы некоего 100 Кб массива. Что я должен сделать, чтобы другой поток гарантированно увидел эти данные ? SK>Общее правило такое — изменения флешатся на записи в volatile и на освобождении лока.
Этого мало. Они должны флешиться при чтении volatile переменной, причем для всех потоков (точнее, всех кешей всех ядер).
Пусть имеем volatile Clazz c; Поток T1 под нее выделил new Clazz и в него что-то записал, может, в класс X, доступный через Clazz через 5 промежуточных классов. Эти данные то ли в RAM, то ди в кеще (working storage) потока T1.
Поток T2 обращается к c. Обращается, а не меняет ее! После этого он по ней полезет в X. При этом должны сброситься в кеш все изменения в Clazz, X и всех 5 промежуточных классах. Но обходить граф никто не будет, да и смысла нет. Значит, должен сбрасываться весь кеш. А поскольку при обращении к c неизвестно, какой поток до этого имел с ней дело, кеш надо сбрасываать у всех ядер.
Здравствуйте, E.K., Вы писали:
EK>Ну не меняется и отлично. Значит используйте synchronized. Я лишь пытался объяснить что volatile это не только memory consistency но и happens-before. Судя по вашим предыдущим комментариям вы этот момент упускаете.
Я об этом и спрашиваю. Как и когда реализуется этот самый happen-before ? Кто его организует для всего графа, висящего на этой ссылке ?
EK>Не понял что еще нужно обосновывать. Семантика volatile и syncronized расписана в деталях в JLS и уже неоднократна объяснялась в этом треде с приведением ссылок на документацию.
Обращение к volatile сбрасывает кеш всех ядер ? Да или нет ?
EK>Если говорить конкретно о вашем примере — он неверен, поскольку происходит изменение общего состояния при отсутствии какой-либо синхронизации.
Можно дать определение общего состояния ? Когда и как оно синхронизируется.
А пример сам по себе не может быть неверен, там ничего нет, кроме моего вопроса.