вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 12.02.12 18:05
Оценка:
Имеется единственный экземпляр класса, к которому в течение часа могут обращаться разные потоки для чтения и модификации. Операции синхронизированы, тут проблем нет. В экземпляре класса много всякого и разного.
Раз в час этот экземпляр должен заменяться на новый экземпляр, который в течение прошедшего часа изготавливался.

По логике вещей эту замену надо проводить атомарно. Но тогда все методы чтения и изменения в течение часа должны добираться к этому экземпляру через блокировку. Пустая трата времени в течение часа.

Между тем, если некий поток все же получит еще раз старое значение этой переменной, ничего плохого не будет по условиям задачи. Просто однажды она должна быть обновлена, а до тех пор можно и старое значение использовать. Минутой ранбше или позже — не важно.

Вопрос — можно ли рискнуть заменить ее без лока ? На С++ я бы рискнул. Указатель либо изменился, либо нет, но не может же он наполовину измениться. Так что он всегда валиден, только вот может быть либо старым, либо новым.

А вот в Яве боюсь. Что, если в это время влезет GC и посчитает нужным удалить старый экземпляр до того, как ссылка обновилась ? Или это невозможно ?

Хотелось бы знать мнение по этому вопросу, как лучше сделать.
With best regards
Pavel Dvorkin
Re: вопрос по блокировке
От: ekamaloff Великобритания  
Дата: 12.02.12 19:02
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Имеется единственный экземпляр класса, к которому в течение часа могут обращаться разные потоки для чтения и модификации. Операции синхронизированы, тут проблем нет. В экземпляре класса много всякого и разного.


PD>Раз в час этот экземпляр должен заменяться на новый экземпляр, который в течение прошедшего часа изготавливался.


PD>По логике вещей эту замену надо проводить атомарно. Но тогда все методы чтения и изменения в течение часа должны добираться к этому экземпляру через блокировку. Пустая трата времени в течение часа.


Если я правильно понял, то операции определенные в данном классе уже синхронизированы, т.е. не требует какой-либо дополнительной блокировки со стороны вызывающего кода. В таком случае разве недостаточно сделать переменную, которая хранит ссылку на текущий экземпляр volatile? В таком случае блокировки для получения данной ссылки не требуется.

PD>А вот в Яве боюсь. Что, если в это время влезет GC и посчитает нужным удалить старый экземпляр до того, как ссылка обновилась ? Или это невозможно ?


Никаких проблем тут с GC быть не должно. В любой момент времени на экземпляр класса ссылается либо статическая volatile переменная через которую организован доступ к объекту, либо локальный фрейм одного из потоков который работает с объектом. Пока эти ссылки есть (а они будут существовать до тех пор пока 1) вы не обновите статическую переменную; 2) все потоки не закончят работать со старым объектом), GC ничего удалять не имеет права.
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
Re: вопрос по блокировке
От: . Великобритания  
Дата: 12.02.12 19:13
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD> Вопрос — можно ли рискнуть заменить ее без лока ? На С++ я бы рискнул. Указатель либо изменился, либо нет, но не может же он наполовину измениться. Так что он всегда валиден, только вот может быть либо старым, либо новым.

Можно, без проблем. Просто объявить volatile.
Если нужны более хитрые операции (CAS например), можно посмотреть AtomicReference.
avalon 1.0rc3 rev 0, zlib 1.2.3.4
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re: вопрос по блокировке
От: zubr Россия  
Дата: 12.02.12 19:21
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Вопрос — можно ли рискнуть заменить ее без лока ? На С++ я бы рискнул. Указатель либо изменился, либо нет, но не может же он наполовину измениться. Так что он всегда валиден, только вот может быть либо старым, либо новым.


Да можно, причем writes and reads are atomic, насколько я помню

PD>А вот в Яве боюсь. Что, если в это время влезет GC и посчитает нужным удалить старый экземпляр до того, как ссылка обновилась ? Или это невозможно ?


невозможно такое поведение. к вашему объекту всегда будет путь от рута какого нибудь треда и его никто не соберет.

PD>Хотелось бы знать мнение по этому вопросу, как лучше сделать.


нужно не забыть про volatile наверное, или использовать AtomicReference, но он здесь оверкил
Re: вопрос по блокировке
От: StanislavK Великобритания  
Дата: 12.02.12 20:25
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Вопрос — можно ли рискнуть заменить ее без лока ? На С++ я бы рискнул. Указатель либо изменился, либо нет, но не может же он наполовину измениться. Так что он всегда валиден, только вот может быть либо старым, либо новым.

Хорошо звучит — "я бы рискнул" без барьеров, не факт, что другие потоки смогут это изменение увидеть.

PD>А вот в Яве боюсь. Что, если в это время влезет GC и посчитает нужным удалить старый экземпляр до того, как ссылка обновилась ? Или это невозможно ?

невозможно, как уже говорили, если есть ссылка из любого потока, то объект никуда не денется.

PD>Хотелось бы знать мнение по этому вопросу, как лучше сделать.

запись в volatile гарантирует, что сработает барьер и все предыдущие изменения будут видны другим потокам. Так же volatile гарантирует happens-before, не знаю насколько это актуально для данного случая.
апдейт ссылок по-любому атомарен.
Re[2]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 13.02.12 02:51
Оценка:
Здравствуйте, ekamaloff, Вы писали:

E>Если я правильно понял, то операции определенные в данном классе уже синхронизированы, т.е. не требует какой-либо дополнительной блокировки со стороны вызывающего кода. В таком случае разве недостаточно сделать переменную, которая хранит ссылку на текущий экземпляр volatile? В таком случае блокировки для получения данной ссылки не требуется.


Вот это я не совсем понимаю. volatile гарантирует, что изменения будут видны другим потокам, то есть что другие потоки не сохранят у себя копии, а должны будут читать из оригинала каждый раз. Это замечательно, но какое отношение это имеет к блокировке ? Чтение переменнной volatile обязательно идет в одну команду ?

Поток A начинает читать переменную. Считывает ее на регистр X.
Поток B изменяет переменную
Поток A заканчивает чтение , засылая из регистра X в регистр Y.
With best regards
Pavel Dvorkin
Re[3]: вопрос по блокировке
От: ekamaloff Великобритания  
Дата: 13.02.12 10:36
Оценка: 18 (1)
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Здравствуйте, ekamaloff, Вы писали:


E>>Если я правильно понял, то операции определенные в данном классе уже синхронизированы, т.е. не требует какой-либо дополнительной блокировки со стороны вызывающего кода. В таком случае разве недостаточно сделать переменную, которая хранит ссылку на текущий экземпляр volatile? В таком случае блокировки для получения данной ссылки не требуется.


PD>Вот это я не совсем понимаю. volatile гарантирует, что изменения будут видны другим потокам, то есть что другие потоки не сохранят у себя копии, а должны будут читать из оригинала каждый раз. Это замечательно, но какое отношение это имеет к блокировке ? Чтение переменнной volatile обязательно идет в одну команду ?


Грубо говоря volatile гарантирует что обновленное значение переменной будет немедленно видно другим потокам. Упрощенное объяснение и пример можно найти здесь, а более формальное определение семантики volatile в контексте "порядка синхронизации" в секции 17.4.4 здесь (англ.):

A write to a volatile variable (§8.3.1.4) v synchronizes-with all subsequent reads of v by any thread (where subsequent is defined according to the synchronization order).

Запись volatile переменной v синхронизирована-с последующими чтениями v любым потоком (где "последующий" определено в контексте "порядка синхронизации")


Кроме того, независимо от того, является ли переменная volatile или нет, запись и чтение переменной ссылочного типа в Java — атомарная операция (это справедливо и для примитивных типов). Как бы ни конкурировали с собой потоки в вашем приложении, у вас всегда есть гарантия того, что переменная содержит либо null или валидную ссылку на объект (без volatile эта переменная может быть устаревшей локальной копией и может вести к непредсказуемым последствиям относительно видимого порядка выполнения некоторых операций — см. пример по первой ссылке выше).

Блокировка при чтении и записи (используя synchronized или java.util.concurrent.lock.*) — это в вашей ситуации альтернатива volatile, которая имеет более сложную семантику:
  1. Обеспечивает немедленное обновления значения переменной во всех потоках (подобно volatile)
  2. Обеспечивает семантику happens-before (так же как и volatile, по-моему начиная с версии 5)
  3. Обеспечивает взаимо-исключающее (мьютекс) выполнение данного участка кода разными потоками

В вашей задаче реально требуется только (1), (2) по большому счету не нужно и в любом случае обеспечивается volatile, (3) — лишнее и может значительно ухудшить производительность кода.


PD>Поток A начинает читать переменную. Считывает ее на регистр X.

PD>Поток B изменяет переменную
PD>Поток A заканчивает чтение , засылая из регистра X в регистр Y.

Как я сказал выше — чтение и запись ссылочной переменной атомарны (это не связано с volatile). Как только поток A считал переменную в локальный стек, его больше не должно волновать что произойдет с исходной volatile переменной.
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
Re[4]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 13.02.12 11:43
Оценка:
Здравствуйте, ekamaloff, Вы писали:

E>Как я сказал выше — чтение и запись ссылочной переменной атомарны (это не связано с volatile). Как только поток A считал переменную в локальный стек, его больше не должно волновать что произойдет с исходной volatile переменной.


В общем, как и в С++. Спасибо, то что надо.
With best regards
Pavel Dvorkin
Re[4]: вопрос по блокировке
От: maxkar  
Дата: 13.02.12 14:09
Оценка: 14 (2) +1
Здравствуйте, ekamaloff, Вы писали:

E>Кроме того, независимо от того, является ли переменная volatile или нет, запись и чтение переменной ссылочного типа в Java — атомарная операция (это справедливо и для примитивных типов).


Запись non-volatile long и dobule все же может быть неатомарной. Non-atomic Treatment of double and long. Ссылки и все остальные примитивные типы — да, атомарны всегда.
Re[2]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 13.02.12 16:17
Оценка:
Здравствуйте, StanislavK, Вы писали:

PD>>Вопрос — можно ли рискнуть заменить ее без лока ? На С++ я бы рискнул. Указатель либо изменился, либо нет, но не может же он наполовину измениться. Так что он всегда валиден, только вот может быть либо старым, либо новым.

SK>Хорошо звучит — "я бы рискнул" без барьеров, не факт, что другие потоки смогут это изменение увидеть.

Гм. Что-то я не понял. Один поток меняет значение переменной. Пусть даже неатомарно. Другой поток читает. Мне что, все такие переменные объявлять volatile ? А массивы ? Иные структуры данных ? Так ведь можно договориться до того, что все изменения в памяти надо проводить атомарно, иначе их другие потоки не увидят
With best regards
Pavel Dvorkin
Re[3]: вопрос по блокировке
От: . Великобритания  
Дата: 13.02.12 16:30
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>>>Вопрос — можно ли рискнуть заменить ее без лока ? На С++ я бы рискнул. Указатель либо изменился, либо нет, но не может же он наполовину измениться. Так что он всегда валиден, только вот может быть либо старым, либо новым.

SK>>Хорошо звучит — "я бы рискнул" без барьеров, не факт, что другие потоки смогут это изменение увидеть.

PD>Гм. Что-то я не понял. Один поток меняет значение переменной. Пусть даже неатомарно. Другой поток читает. Мне что, все такие переменные объявлять volatile ? А массивы ? Иные структуры данных ? Так ведь можно договориться до того, что все изменения в памяти надо проводить атомарно, иначе их другие потоки не увидят

Все примитивные значения (т.е. примитивные типы + ссылка) к которым возможен доступ из разных потоков очень желательно объявлять volatile. Скажем, такой код:
class MyObj
{
  int value;
  MyObj(int value){this.value=value;}
}
...
MyObj shared=new MyObj(1);
...
shared = new MyObj(2); /// thread 1
...
System.out.println(shared.value);/// thread 2

может вывести значение 0, т.к. значение в shared может быть присвоено до того, как 2 присвоится в value.
Если ты объявишь shared как volatile, то будет гарантия happens-before.

Ещё 64-битные значения (т.е. long и double), не помеченные как volatile могут быть испорчены, т.к. присвоение может быть неатомарным (как минимум по спеке, но может быть не на практике).
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[3]: вопрос по блокировке
От: StanislavK Великобритания  
Дата: 13.02.12 22:24
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Здравствуйте, StanislavK, Вы писали:


PD>>>Вопрос — можно ли рискнуть заменить ее без лока ? На С++ я бы рискнул. Указатель либо изменился, либо нет, но не может же он наполовину измениться. Так что он всегда валиден, только вот может быть либо старым, либо новым.

SK>>Хорошо звучит — "я бы рискнул" без барьеров, не факт, что другие потоки смогут это изменение увидеть.

PD>Гм. Что-то я не понял. Один поток меняет значение переменной. Пусть даже неатомарно. Другой поток читает. Мне что, все такие переменные объявлять volatile ? А массивы ? Иные структуры данных ? Так ведь можно договориться до того, что все изменения в памяти надо проводить атомарно, иначе их другие потоки не увидят


Че-то вы там плаваете. Атомарность тут не при чем, да и барьеры применяется не к переменным в отдельности.

volatile гарантирует 3 вещи:

1. Happens-before. Попросту говоря гарантия того, что то, что в коде написано до чтения/записи volatile действительно случится до этого момента. Эта темя хорошо раскрыта в проблеме синглетона в java до 1.5. Вот тут есть пара ссылок на эту тему: http://rsdn.ru/forum/java/4601604.1.aspx
Автор: StanislavK
Дата: 03.02.12
Действует как на уровне компилятора (он не делает оптимизаций, которые меняют порядок) и на уровне процессора.
2. memory visibility. Гарантия того, что изменение значения переменной увидят другие потоки. В JVM spec говорится, что у каждого потока есть своя working memory и что есть main memory, что поток может обновить значение переменной в working memory и оно, в принципе, вообще может не попасть в общую память, видимую всем другим потокам. На практике это всего-лишь метафора для всевозможных оптимизаций, кешей процессора, о том, что в процессоре операции могут происходить (или результаты могут быть видны) не в том порядке, как кажется(это, кстати, тоже, и к happens-before относится). В общем, для всего, что может помешать потокам видеть одни и те же данные и в ожидаемом порядке. Кеши и оптимизации процессора лечатся барьерами, эта тема раскрыта вот тут: http://en.wikipedia.org/wiki/Memory_barrier. Т.к. используются барьеры, то на практике (но не в теории, memory model этого не гарантирует) запись в volatile переменную означает, что все, что было изменено до этой записи будет видно другим потокам.
3. atomicity для примитивных типов и ссылок. Надо заметить, что проблема может возникнуть _только_ для 64-х битных типов на 32-х битных процессорах, что сейчас большая редкость. Раскрывать тут особо нечего.

К слову о С++, то там это тоже, появилось (наконец!!!) в новом стандарте. Деталей с ходу не нашел, но вот тут описана мотивация: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2016.html

Так, что вот.. многопоточность, она на самом деле много хуже, чем кажется и читайте Java Memory Model и JVM spec про потоки, там еще много чего написано.
Re[4]: вопрос по блокировке
От: E.K. Великобритания  
Дата: 14.02.12 10:45
Оценка:
Здравствуйте, StanislavK, Вы писали:


SK>volatile гарантирует 3 вещи:


SK>3. atomicity для примитивных типов и ссылок. Надо заметить, что проблема может возникнуть _только_ для 64-х битных типов на 32-х битных процессорах, что сейчас большая редкость. Раскрывать тут особо нечего.


Не так. Атомарность записи/чтения ссылочных и примитивных типов (за исключением long и double), как уже упоминалось выше, гарантирует язык как таковой, а не volatile. Смотреть здесь или здесь.

volatile лишь добавляет семантику happens-before и memory-consistency (как следствие happens-before) — т.е. любой поток видит 1) последнее записанное значение переменной 2) а также эффект от всех действий совершенных перед записью этой переменной.

Кроме того, volatile расширяет атомарность записи и чтения на long и double.
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
Re[4]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 12:45
Оценка:
Здравствуйте, StanislavK, Вы писали:


SK>Так, что вот.. многопоточность, она на самом деле много хуже, чем кажется и читайте Java Memory Model и JVM spec про потоки, там еще много чего написано.


Да, наверное ты прав. Я с потоками имел дело не раз на С++. Там нет необходимости учитывать этот memory barrier, так как примитивы синхронизации Win API обеспечивают запись измененных данных в RAM. Но там нет никакой working memory у потока (хотя кэш процессора, конечно, есть). Если в Яве ввели еще некую локальную память для потока, то понятно, что необходимо обеспечить ее синхронизацию с основной памятью.

Для уточнения — такой вопрос. Пусть я в одном потоке записал элементы некоего 100 Кб массива. Что я должен сделать, чтобы другой поток гарантированно увидел эти данные ?
With best regards
Pavel Dvorkin
Re[5]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 13:15
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Да, наверное ты прав. Я с потоками имел дело не раз на С++. Там нет необходимости учитывать этот memory barrier, так как примитивы синхронизации Win API обеспечивают запись измененных данных в RAM. Но там нет никакой working memory у потока (хотя кэш процессора, конечно, есть). Если в Яве ввели еще некую локальную память для потока, то понятно, что необходимо обеспечить ее синхронизацию с основной памятью.

Если в java ты используешь synchronized или java.util.concurrent.locks.*, это примерно эквивалентно примитивам синхронизации WinAPI (если ошибаюсь, поправьте!).
Вопрос в том, чтобы избежать явной синхронизации, которая вызывает проблемы с масшабируемостью на кучу ядер. Иногда можно избежать лишней синхронизации, но для явы только начиная с 1.5.
А в C++ working memory тоже есть, там всё гораздо хуже. Скажем, Double Check locking реализовать было(?) нельзя, только в каких-то версиях компиляторов таки можно.

PD>Для уточнения — такой вопрос. Пусть я в одном потоке записал элементы некоего 100 Кб массива. Что я должен сделать, чтобы другой поток гарантированно увидел эти данные ?

Если переносишь код с WinAPI, то не парься c volatile, используй ReadWriteLock — всё будет хорошо.
Для подробностей лучше покажи примерный код, будет проще подумать как же лучше сделать.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[5]: вопрос по блокировке
От: E.K. Великобритания  
Дата: 14.02.12 13:24
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Для уточнения — такой вопрос. Пусть я в одном потоке записал элементы некоего 100 Кб массива. Что я должен сделать, чтобы другой поток гарантированно увидел эти данные ?


Нужно защитить доступ к массиву с помощью synchronized.

Т.е. как-то так (пусть вас не напрягает копирование массивов, оно здесь для простоты примера)

private final byte[] array = new byte[100000];

public void write(int offset, byte[] data) {
  synchronized(array) {
    System.copyarray(data, 0, array, offset, data.length);
  }
}

public void read(int offset, byte[] buffer) {
  synchronized(array) {
    System.copyarray(buffer, 0, array, offset, buffer.length)
  }
}


Синхронизировать на самом массиве конечно необязательно — хотя и вполне логично в данном случае. Главное чтобы синхронизация была на одном и том же мониторе (объекте), который неизменен на протяжении синхронизированного доступа
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
Re[6]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 13:34
Оценка:
Здравствуйте, ., Вы писали:

.>А в C++ working memory тоже есть, там всё гораздо хуже. Скажем, Double Check locking реализовать было(?) нельзя, только в каких-то версиях компиляторов таки можно.


Вообще-то в С++ , по крайней мере до C++0x, ничего об это вообще нет, так как и понятия потока нет. __beginthreadex к стандарту не относится. Синхронизация делается средствами Win API для Windows (С линукс я не работаю). И проблем с этим вроде как не было.

PD>>Для уточнения — такой вопрос. Пусть я в одном потоке записал элементы некоего 100 Кб массива. Что я должен сделать, чтобы другой поток гарантированно увидел эти данные ?

.>Если переносишь код с WinAPI, то не парься c volatile, используй ReadWriteLock — всё будет хорошо.

Нет, это не ответ. Будет ли нет ?

.>Для подробностей лучше покажи примерный код, будет проще подумать как же лучше сделать.


Да бога ради, вот пример (псевдокод, конечно)



class A
{
  int[] a = new int[1000];
  int s;
  void f() // вызывается из mainThread
{ 
 for (int i = 0; i < 1000; i++)
  a[i] = i;
 Thread2.start();
}

int sum() // вызывается из Thread2
{
 for (int i = 0; i < 1000; i++)
  s+=i;
}


Будет сумма корректной или нет ?
With best regards
Pavel Dvorkin
Re[7]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 13:44
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:


.>>А в C++ working memory тоже есть, там всё гораздо хуже. Скажем, Double Check locking реализовать было(?) нельзя, только в каких-то версиях компиляторов таки можно.

PD>Вообще-то в С++ , по крайней мере до C++0x, ничего об это вообще нет, так как и понятия потока нет. __beginthreadex к стандарту не относится. Синхронизация делается средствами Win API для Windows (С линукс я не работаю). И проблем с этим вроде как не было.
Вроде я слышал, что в какой-то стандарт С++ решили внести memory model как в java. Подробностей не знаю.

Проблемы были, если не использовались явные функции синхронизации. Аналогично в java 1.4.

PD>>>Для уточнения — такой вопрос. Пусть я в одном потоке записал элементы некоего 100 Кб массива. Что я должен сделать, чтобы другой поток гарантированно увидел эти данные ?

.>>Если переносишь код с WinAPI, то не парься c volatile, используй ReadWriteLock — всё будет хорошо.
PD>Нет, это не ответ. Будет ли нет ?
Будет. Даже лучше, чем с простыми локами в C++, т.к. read-операции не будут друг-друга блокировать.

.>>Для подробностей лучше покажи примерный код, будет проще подумать как же лучше сделать.

PD>Да бога ради, вот пример (псевдокод, конечно)

PD>Будет сумма корректной или нет ?

Да тут вообще многопоточного доступа нет. Тред запускается только после того, как всё запишется. Интуитивно думаю, что запуск треда гарантирует happens before, хотя ссылку на спеку не дам, искать лень.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[6]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 13:45
Оценка:
Здравствуйте, E.K., Вы писали:

EK>Здравствуйте, Pavel Dvorkin, Вы писали:


PD>>Для уточнения — такой вопрос. Пусть я в одном потоке записал элементы некоего 100 Кб массива. Что я должен сделать, чтобы другой поток гарантированно увидел эти данные ?


EK>Нужно защитить доступ к массиву с помощью synchronized.


<skipped>

EK>Синхронизировать на самом массиве конечно необязательно — хотя и вполне логично в данном случае. Главное чтобы синхронизация была на одном и том же мониторе (объекте), который неизменен на протяжении синхронизированного доступа


Что-то здесь не то.

Я понимаю, что во время работы write не сможет read прочитать данные. Это хорошо, но вопрос-то не в этом.

Пусть 100% гарантированно read выполняется после write. synchronized тогда смысла не имеет — в этот монитор никто войти и не попытается, пока он занят. Но мне же StanislavK доказывал, что у потока есть своя memory (ну ладно, деталей этого я не знаю, а вот что уж точно знаю, это то, что у ядра есть кэш.). Предположим, что поток write записал в элементы массива с нулевого по 999 (это ему кажется, что он в RAM записал, а попало только в кеш или эту Java working memory). Поток read читает через пару минут и ? Что он прочитает ? Или сам факт входа в synchronized работает как flush all thread memory ?
With best regards
Pavel Dvorkin
Re[8]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 13:50
Оценка:
Здравствуйте, ., Вы писали:

.>Вроде я слышал, что в какой-то стандарт С++ решили внести memory model как в java. Подробностей не знаю.


Я тоже не в курсе. Пропустим.

PD>>Нет, это не ответ. Будет ли нет ?

.>Будет. Даже лучше, чем с простыми локами в C++, т.к. read-операции не будут друг-друга блокировать.

.>>>Для подробностей лучше покажи примерный код, будет проще подумать как же лучше сделать.

PD>>Да бога ради, вот пример (псевдокод, конечно)

PD>>Будет сумма корректной или нет ?

.>Да тут вообще многопоточного доступа нет. Тред запускается только после того, как всё запишется. Интуитивно думаю, что запуск треда гарантирует happens before, хотя ссылку на спеку не дам, искать лень.

Хорошо, тогда запустим тред заранее


class A
{
  int[] a = new int[1000];
  int s;
  void main()
{
 Thread2.start();
 f();
 Thread2.join();
}

  void f() // вызывается из mainThread
{ 
 for (int i = 0; i < 1000; i++)
  a[i] = i;
}

int sum() // вызывается из Thread2
{
 Thread.sleep(10000);
 for (int i = 0; i < 1000; i++)
  s+=i;
}
With best regards
Pavel Dvorkin
Re[8]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 13:52
Оценка:
Здравствуйте, ., Вы писали:

PD>>Будет сумма корректной или нет ?

.>Да тут вообще многопоточного доступа нет. Тред запускается только после того, как всё запишется. Интуитивно думаю, что запуск треда гарантирует happens before, хотя ссылку на спеку не дам, искать лень.
Тут обсуждение частично подтверждает мою интуицию: http://stackoverflow.com/questions/7651226/java-happend-before-thread-start
Хотя там говорится об ExecutorService, мне кажется что и голые треды работают так же.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[9]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 13:58
Оценка:
Здравствуйте, 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 что-нибудь отправиться, вот и всё — неуловимый баг.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[7]: вопрос по блокировке
От: E.K. Великобритания  
Дата: 14.02.12 14:07
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:


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
Re[10]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 14:09
Оценка:
Здравствуйте, ., Вы писали:

.>Здравствуйте, Pavel Dvorkin, Вы писали:

PD>>Хорошо, тогда запустим тред заранее
.>Вот тогда да, нужна синхронизация или volatile. С синхронизацией примерно всё понятно, я полагаю, а с volatile можно делать так:

Нет, все равно неясно.

volatile гарантирует, что this.a будет атомарно равно a из int []a = new int[1000]; Это хорошо, но вопрос не об этом. Еще раз посмотри мой пример. Ссылку на массив я там не меняю вообще, поэтому о ее валидности заботиться незачем. Но я меняю данные в массиве. Если уж volatile, то надо объявить volatile все элементы массива. . А вдруг первый поток записал в a[10], а эти данные остались в кеше ядра первого потока и не попали в RAM ? А если для a[10] попали, то это не гарантирует. что попали для a[100].

И синхронизация по ссылке с помощью лока там тоже ни к чему по той же причине : я не меняю ссылку, а меняю данные по ссылке. Лочить надо, стало быть, все элементы массива
With best regards
Pavel Dvorkin
Re[11]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 14:13
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:


.>>Вот тогда да, нужна синхронизация или 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 для всех потоков потоков.

Ту же гарантию дают и локи, но они сильнее влияют на производительность.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[8]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 14:19
Оценка:
Здравствуйте, 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 для поменявшего ссылку потока.

Что-то мне все это сомнительно.
With best regards
Pavel Dvorkin
Re[12]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 14:22
Оценка:
Здравствуйте, ., Вы писали:


PD>>И синхронизация по ссылке с помощью лока там тоже ни к чему по той же причине : я не меняю ссылку, а меняю данные по ссылке. Лочить надо, стало быть, все элементы массива

.>Не только атомарно (хотя на атомарность оно не влияет, т.к. это reference, который и так атомарен), а с гарантией happens-before: т.е. действия (изменение эл-тов массива в твоём случае) совершенные до this.a=a будут выгядеть happens-before для всех потоков потоков.

Да нет там у меня this.a =a! Там всего одна ссылка, и она не меняется. А вот по ней в элементы массива пишет один поток, а читает через 10 секунд другой.
Так что, надо лочить все элементы массива ?
With best regards
Pavel Dvorkin
Re[11]: вопрос по блокировке
От: E.K. Великобритания  
Дата: 14.02.12 14:22
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:


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
Re[9]: вопрос по блокировке
От: E.K. Великобритания  
Дата: 14.02.12 14:27
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:


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
Re[13]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 14:27
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Да нет там у меня this.a =a! Там всего одна ссылка, и она не меняется. А вот по ней в элементы массива пишет один поток, а читает через 10 секунд другой.

PD>Так что, надо лочить все элементы массива ?
Конечно надо. Ровно так же как и в С++. И количество секунд тут ничего не гарантирует.
Имеется в виду не каждый элемент лочить отдельно, а достаточно залочить некий лок, который общий для всего массива (как показал E.K.).
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[5]: вопрос по блокировке
От: StanislavK Великобритания  
Дата: 14.02.12 14:27
Оценка:
Здравствуйте, E.K., Вы писали:

EK>Здравствуйте, StanislavK, Вы писали:



SK>>volatile гарантирует 3 вещи:


SK>>3. atomicity для примитивных типов и ссылок. Надо заметить, что проблема может возникнуть _только_ для 64-х битных типов на 32-х битных процессорах, что сейчас большая редкость. Раскрывать тут особо нечего.


EK>Кроме того, volatile расширяет атомарность записи и чтения на long и double.


Согласен. Я просто коряво выразился. Имел ввиду, что при volatile всегда соблюдается atomicity, независимо от типа.
Re[12]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 14:29
Оценка:
Здравствуйте, 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!


А вот это обоснуйте, пожалуйста. На вот этом примере

http://rsdn.ru/forum/java/4617197.1.aspx
Автор: Pavel Dvorkin
Дата: 14.02.12


Ставить volatile там незачем и некому — ни одна ссылка там не меняется вообще.

А второе ваше утверждение мне совсем не очевидно. По этой ссылке могут быть мегабайты информации. Они все будут сброшены в RAM ? Когда ? При обращении к volatile ссылке (менять-то ее никто не меняет) ?
With best regards
Pavel Dvorkin
Re[5]: вопрос по блокировке
От: StanislavK Великобритания  
Дата: 14.02.12 14:31
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Здравствуйте, StanislavK, Вы писали:



SK>>Так, что вот.. многопоточность, она на самом деле много хуже, чем кажется и читайте Java Memory Model и JVM spec про потоки, там еще много чего написано.

PD>Да, наверное ты прав. Я с потоками имел дело не раз на С++. Там нет необходимости учитывать этот memory barrier, так как примитивы синхронизации Win API обеспечивают запись измененных данных в RAM. Но там нет никакой working memory у потока (хотя кэш процессора, конечно, есть). Если в Яве ввели еще некую локальную память для потока, то понятно, что необходимо обеспечить ее синхронизацию с основной памятью.
Там все точно так же. Только семантика реализована на уровне API. В java она, большей частью ( ), на уровне языка.

PD>Для уточнения — такой вопрос. Пусть я в одном потоке записал элементы некоего 100 Кб массива. Что я должен сделать, чтобы другой поток гарантированно увидел эти данные ?

Общее правило такое — изменения флешатся на записи в volatile и на освобождении лока.
Re[10]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 14:34
Оценка:
Здравствуйте, 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, то (по-вашему) еще и сброс всего кеша всех тредов. В это я как-то плохо верю.
With best regards
Pavel Dvorkin
Re[13]: вопрос по блокировке
От: E.K. Великобритания  
Дата: 14.02.12 14:37
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Еще раз объясняю — не меняется в моем примере ссылка!!! Данные по ссылке меняются, а сама ссылка нет. Ссылка 4 байта, данных по ней много, целый граф из множества классов там может висеть.


Ну не меняется и отлично. Значит используйте synchronized. Я лишь пытался объяснить что volatile это не только memory consistency но и happens-before. Судя по вашим предыдущим комментариям вы этот момент упускаете.


PD>А вот это обоснуйте, пожалуйста. На вот этом примере


PD>http://rsdn.ru/forum/java/4617197.1.aspx
Автор: Pavel Dvorkin
Дата: 14.02.12


PD>Ставить volatile там незачем и некому — ни одна ссылка там не меняется вообще.


Не понял что еще нужно обосновывать. Семантика volatile и syncronized расписана в деталях в JLS и уже неоднократна объяснялась в этом треде с приведением ссылок на документацию.

Если говорить конкретно о вашем примере — он неверен, поскольку происходит изменение общего состояния при отсутствии какой-либо синхронизации. Вам нужен synchronized, как я уже показывал здесь
Автор: E.K.
Дата: 14.02.12
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
Re[11]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 14:41
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Имеется в виду, что замена ссылки всегда атомарна, как утверждает Sun. Но если она не volatile, то это просто замена 4(8) байт. А если volatile, то (по-вашему) еще и сброс всего кеша всех тредов. В это я как-то плохо верю.

Не всех, а тех, которые обращаются к этой volatile переменной в данный момент. И не всего кеша, а тот который должен happens-before обращения к этой переменной.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[14]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 14:42
Оценка:
Здравствуйте, ., Вы писали:

PD>>Так что, надо лочить все элементы массива ?

.>Конечно надо. Ровно так же как и в С++. И количество секунд тут ничего не гарантирует.

С++ тут ни при чем. С таким же успехом можно писать на ассемблере, где локов вообще нет, или на Delphi, где их тоже нет. Как и в С++.

А в Win API локов на участки памяти тоже нет. Есть критическая секция и мютекс, но они понятия не имеют, что они защищают, это нигде в коде не указывается. Они просто синхронизируют входы в участок кода, и при этом выполняют сброс кеша ядра. Поэтому Ява и кто угодно не может по крайней мере в Windows лочить память — не может она делать то, что не позволяет подлежащая платформа.

.>Имеется в виду не каждый элемент лочить отдельно, а достаточно залочить некий лок, который общий для всего массива (как показал E.K.).


То есть ты утверждаешь что при любом обращении к synchronized переменной вся паять всех кешей сбрасывается в RAM ? Иного объяснения я не вижу. Знать, какой поток изменил данные по этой synchronized ссылке никто не может.
With best regards
Pavel Dvorkin
Re[11]: вопрос по блокировке
От: E.K. Великобритания  
Дата: 14.02.12 14:44
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:


EK>>Не всех данных, а только тех, которые были изменены некими действиями совершенными в потоке до записи volatile переменной (и видны только тем потокам которые впоследствии из этой volatile переменной прочитали)!


PD>То есть при обращении к volatile переменной сбрасываются все изменения в кэше всех потоков ? При любом обращении ?


Только в том потоке в котором вы читаете. И только те изменения которые были сделаны до записи volatile.


PD>Аргументируйте. А если эта ссылка косвенно ссылается на 200 Мб всяких данных ?


Значит все 200МБ будут видимы вашему потоку. Конкретная реализация JVM может вообще решить не использовать никакого кеша потока для этих данных — кто его знает? Главное — то, что вам гарантирует спецификация языка.


EK>>Вообще не понял что имеется ввиду.


PD>Имеется в виду, что замена ссылки всегда атомарна, как утверждает Sun. Но если она не volatile, то это просто замена 4(8) байт. А если volatile, то (по-вашему) еще и сброс всего кеша всех тредов. В это я как-то плохо верю.


Не всего кеша и не всех потоков. Не выкручивайте ответы наизнанку.

Я бы посоветовал еще посмотреть первый ответ здесь:

http://stackoverflow.com/questions/1351168/volatile-semantic-with-respect-to-other-fields
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
Re[6]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 14:49
Оценка:
Здравствуйте, 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 неизвестно, какой поток до этого имел с ней дело, кеш надо сбрасываать у всех ядер.

Так ?
With best regards
Pavel Dvorkin
Re[14]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 14:52
Оценка:
Здравствуйте, E.K., Вы писали:

EK>Ну не меняется и отлично. Значит используйте synchronized. Я лишь пытался объяснить что volatile это не только memory consistency но и happens-before. Судя по вашим предыдущим комментариям вы этот момент упускаете.


Я об этом и спрашиваю. Как и когда реализуется этот самый happen-before ? Кто его организует для всего графа, висящего на этой ссылке ?

EK>Не понял что еще нужно обосновывать. Семантика volatile и syncronized расписана в деталях в JLS и уже неоднократна объяснялась в этом треде с приведением ссылок на документацию.


Обращение к volatile сбрасывает кеш всех ядер ? Да или нет ?

EK>Если говорить конкретно о вашем примере — он неверен, поскольку происходит изменение общего состояния при отсутствии какой-либо синхронизации.


Можно дать определение общего состояния ? Когда и как оно синхронизируется.
А пример сам по себе не может быть неверен, там ничего нет, кроме моего вопроса.
With best regards
Pavel Dvorkin
Re[12]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 14:56
Оценка:
Здравствуйте, ., Вы писали:

.>Здравствуйте, Pavel Dvorkin, Вы писали:


PD>>Имеется в виду, что замена ссылки всегда атомарна, как утверждает Sun. Но если она не volatile, то это просто замена 4(8) байт. А если volatile, то (по-вашему) еще и сброс всего кеша всех тредов. В это я как-то плохо верю.

.>Не всех, а тех, которые обращаются к этой volatile переменной в данный момент.

Что значит "тех, которые обращаются " ? Обращается-то один. И зачем его кеш сбрасывать, когда надо сбрасывать кеш того потока, который по этой volatile ссылке что-то там модифицировал ?

>И не всего кеша, а тот который должен happens-before обращения к этой переменной.


Это как, можно узнать ? Кеш ядра прозрачен для программы, никто не знает, какая переменая где в кеше находится и есть ли она там вообще.
With best regards
Pavel Dvorkin
Re[15]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 14:58
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>С++ тут ни при чем. С таким же успехом можно писать на ассемблере, где локов вообще нет, или на Delphi, где их тоже нет. Как и в С++.

В ассемблере вообще-то специальные инструкции есть для барьеров. Но я тут не копенгаген.

PD>То есть ты утверждаешь что при любом обращении к synchronized переменной вся паять всех кешей сбрасывается в RAM ? Иного объяснения я не вижу. Знать, какой поток изменил данные по этой synchronized ссылке никто не может.

Сам поток при входе-выходе знает. Не понимаю зачем это знать другим потокам. Посмотри вот этот код:
int a=0, b=0;
...
a=1;
//synchronized(this){
b=1;
//}

если переменные non-volatile то если один поток прочитает b и там будет 1, то переменная a может иметь значение 0. Хотя это выглядит дико, но оптимизаторы/кеши имеют право.
Если убрать комментарии или переменную b объявить volatile, то спецификация языка java 1.5 гарантирует что a==1.

Всё абсолютно то же самое и в C++ c синхронизацией. Только есть разница для volatile — там гарантии тоже нет для некоторых компиляторов.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[15]: вопрос по блокировке
От: E.K. Великобритания  
Дата: 14.02.12 15:00
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:


EK>>Ну не меняется и отлично. Значит используйте synchronized. Я лишь пытался объяснить что volatile это не только memory consistency но и happens-before. Судя по вашим предыдущим комментариям вы этот момент упускаете.


PD>Я об этом и спрашиваю. Как и когда реализуется этот самый happen-before ? Кто его организует для всего графа, висящего на этой ссылке ?


Зависит от реализации VM.

PD>Обращение к volatile сбрасывает кеш всех ядер ? Да или нет ?


По спецификации обязано сбрасывать только кеш того потока который делает volatile-read и только того состояния которое было изменено до последнего volatile-write.


PD>Можно дать определение общего состояния ?


Мутабельное (изменяемое) состояние к которому могут иметь одновременный доступ (на запись или чтение) два и более потока исполнения.

PD>Когда и как оно синхронизируется.


Когда — при чтении или записи данных.

Как — synchronized, volatile или java.util.concurrent.lock.* в зависимости от задачи. Есть еще AtomicXXX классы которые обеспечивают доступ к общему состоянию без необходимости синхронизации
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
Re[12]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 15:03
Оценка:
Здравствуйте, E.K., Вы писали:


PD>>То есть при обращении к volatile переменной сбрасываются все изменения в кэше всех потоков ? При любом обращении ?


EK>Только в том потоке в котором вы читаете. И только те изменения которые были сделаны до записи volatile.


Что в том потоке ? Он обращается к volatile переменной. Она ссылается на данные, изготовленные другим потоком. каким — бог знает. Поэтому данные в кеше неизвестно какого потока.


PD>>Аргументируйте. А если эта ссылка косвенно ссылается на 200 Мб всяких данных ?


EK>Значит все 200МБ будут видимы вашему потоку. Конкретная реализация JVM может вообще решить не использовать никакого кеша потока для этих данных — кто его знает? Главное — то, что вам гарантирует спецификация языка.


Что она опять же гарантирует ? Что при обращении к synchronized переменной все данные, доступные из нее, будут видны потоку, который выполнил это обращение ?

EK>>>Вообще не понял что имеется ввиду.


PD>>Имеется в виду, что замена ссылки всегда атомарна, как утверждает Sun. Но если она не volatile, то это просто замена 4(8) байт. А если volatile, то (по-вашему) еще и сброс всего кеша всех тредов. В это я как-то плохо верю.


EK>Не всего кеша и не всех потоков. Не выкручивайте ответы наизнанку.


Я ничьи ответы не выкручиваю. Я просто объясняю, что иначе это сделать невозможно. IMHO. Если считаете, что возможно — объясните как.

Может, я плохо понял, но там опять о самой ссылке, а не о том, на что она ссылается.
With best regards
Pavel Dvorkin
Re[16]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 15:05
Оценка:
Здравствуйте, ., Вы писали:

>>Иного объяснения я не вижу. Знать, какой поток изменил данные по этой synchronized ссылке никто не может.

.>Сам поток при входе-выходе знает.

Я уже не знаю, как объяснять. Давай закончим.


.>Всё абсолютно то же самое и в C++ c синхронизацией. Только есть разница для volatile — там гарантии тоже нет для некоторых компиляторов.


Я об одном, ты совсем о другом. Мы друг друга не понимаем.
With best regards
Pavel Dvorkin
Re[13]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 15:06
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Что в том потоке ? Он обращается к volatile переменной. Она ссылается на данные, изготовленные другим потоком. каким — бог знает. Поэтому данные в кеше неизвестно какого потока.

Он обращается к значению volatile переменной, которая была до этого записана другим потоком. Когда другой поток записывал, он сбросил все свои кеши, которые были happens-before. Где чего неизвестно??
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[7]: вопрос по блокировке
От: StanislavK Великобритания  
Дата: 14.02.12 15:07
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Здравствуйте, StanislavK, Вы писали:


PD>>>Для уточнения — такой вопрос. Пусть я в одном потоке записал элементы некоего 100 Кб массива. Что я должен сделать, чтобы другой поток гарантированно увидел эти данные ?

SK>>Общее правило такое — изменения флешатся на записи в volatile и на освобождении лока.
PD>Этого мало.
Мало чего?

PD>Они должны флешиться при чтении volatile переменной, причем для всех потоков (точнее, всех кешей всех ядер).

Кому должны? Флешиться она должны так как описано в спеке и не более. И не понятно, как соотносятся "все потоки" и "все ядра".

PD>Пусть имеем volatile Clazz c; Поток T1 под нее выделил new Clazz и в него что-то записал, может, в класс X, доступный через Clazz через 5 промежуточных классов. Эти данные то ли в RAM, то ди в кеще (working storage) потока T1.

PD>Поток T2 обращается к c. Обращается, а не меняет ее! После этого он по ней полезет в X. При этом должны сброситься в кеш все изменения в Clazz, X и всех 5 промежуточных классах. Но обходить граф никто не будет, да и смысла нет. Значит, должен сбрасываться весь кеш. А поскольку при обращении к c неизвестно, какой поток до этого имел с ней дело, кеш надо сбрасываать у всех ядер.

Чтение volatile гарантирует, что произойдет "запись" обратно из "главной памяти" в "память птока". Запись гарантирует обраное — "запись" из "память потока" в "главную память".
В описаном сценарии получается, что когда поток T1 записал в volatile, то его пямять флешнулась в "главную память". Если он после этого делал изменения, то не факт, что они будут в "главной памяти".
Когда T2 обращается к volatile, то гарантируется, что он увидит то, что было в "главной памяти" на момент обращания.

PD>Так ?

Не уверен. Не понимаю при чем тут все ядра.
Re[16]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 15:07
Оценка:
Здравствуйте, E.K., Вы писали:

Ладно, давай закончим. Мы друг друга не понимаем. Мы о разных вещах говорим.
With best regards
Pavel Dvorkin
Re[13]: вопрос по блокировке
От: E.K. Великобритания  
Дата: 14.02.12 15:10
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:


PD>Может, я плохо понял, но там опять о самой ссылке, а не о том, на что она ссылается.


По ссылке именно об эффекте volatile на состояние и видимость других переменных. Почитайте внимательно.
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
Re[17]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 15:14
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

>>>Иного объяснения я не вижу. Знать, какой поток изменил данные по этой synchronized ссылке никто не может.

.>>Сам поток при входе-выходе знает.
PD>Я уже не знаю, как объяснять. Давай закончим.
Ты по-моему на процесс с другой стороны смотришь вот и странным кажется. Там всё наоборот. Не поток "вытягивает" данные из других потоков, а наоборот, поток помещает/забирает данные в/из разделяемую память в точно обозначенные кодом моменты времени.

.>>Всё абсолютно то же самое и в C++ c синхронизацией. Только есть разница для volatile — там гарантии тоже нет для некоторых компиляторов.

PD>Я об одном, ты совсем о другом. Мы друг друга не понимаем.
Ну ладно, не знаю тогда что не ясно. Вроде всё просто. Видимо ты не до конца понимаешь как оно всё работает и в С++. Почитай литературу, там всё более чётко объясняется.
Если что, показывай пример кода, помедитируем.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[8]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 15:17
Оценка:
Здравствуйте, StanislavK, Вы писали:

PD>>Пусть имеем volatile Clazz c; Поток T1 под нее выделил new Clazz и в него что-то записал, может, в класс X, доступный через Clazz через 5 промежуточных классов. Эти данные то ли в RAM, то ди в кеще (working storage) потока T1.

PD>>Поток T2 обращается к c. Обращается, а не меняет ее! После этого он по ней полезет в X. При этом должны сброситься в кеш все изменения в Clazz, X и всех 5 промежуточных классах. Но обходить граф никто не будет, да и смысла нет. Значит, должен сбрасываться весь кеш. А поскольку при обращении к c неизвестно, какой поток до этого имел с ней дело, кеш надо сбрасываать у всех ядер.

SK>Чтение volatile гарантирует, что произойдет "запись" обратно из "главной памяти" в "память птока". Запись гарантирует обраное — "запись" из "память потока" в "главную память".


SK>В описаном сценарии получается, что когда поток T1 записал в volatile, то его пямять флешнулась в "главную память".


Так, допустим.

>Если он после этого делал изменения, то не факт, что они будут в "главной памяти".


Тоже допустим.

SK>Когда T2 обращается к volatile, то гарантируется, что он увидит то, что было в "главной памяти" на момент обращания.


Резюме.

Поток Т1 записал в volatile, его память переписана в RAM. Он потом делал изменения по этой ссылке в классе X, но не менял саму ссылку (то есть не менял volatile переменную, а менял данные, по ней доступные по ссылке). Поток T2 обратился к этим данным и увидел то, что было до этих изменений. (может, конечно, и изменения увидел, но не гарантируется)

Я ничего не переврал ? Вроде нет.

Тогда возвращаемся к исходному вопросу в моем примере. Как мне гарантировать, что изменения в классе X, которые он сделал после изменения volatile переменной, будут видны T2 ?
Более того, как мне гарантировать, что изменения в классе X, которые он сделал, в случае , если он вообще не менял саму ссылку c , будут видны потоку T2 ?

PD>>Так ?

SK>Не уверен. Не понимаю при чем тут все ядра.

Попробуй ответить на мой вопрос, поймешь
With best regards
Pavel Dvorkin
Re[18]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 15:20
Оценка:
Здравствуйте, ., Вы писали:

.>Если что, показывай пример кода, помедитируем.


Помедитируй на моем примере с массивом и попробуй объяснить (себе) как изменения в нем окажутся видны первому потоку и почему. Ссылка там одна-единственная на все про все.
With best regards
Pavel Dvorkin
Re[9]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 15:23
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Я ничего не переврал ? Вроде нет.

Да. Ок.

PD>Тогда возвращаемся к исходному вопросу в моем примере. Как мне гарантировать, что изменения в классе X, которые он сделал после изменения volatile переменной, будут видны T2 ?

PD>Более того, как мне гарантировать, что изменения в классе X, которые он сделал, в случае , если он вообще не менял саму ссылку c , будут видны потоку T2 ?
Залочить оба треда на одном локе перед изменением и перед чтением этих данных.
При освобождении лока пишуший тред сбросит все happens-before данные в главную память и читающий тред при взятии лока прочтёт их из главной памяти.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[19]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 15:29
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

.>>Если что, показывай пример кода, помедитируем.

PD>Помедитируй на моем примере с массивом и попробуй объяснить (себе) как изменения в нем окажутся видны первому потоку и почему. Ссылка там одна-единственная на все про все.
Без локов — могут не оказаться!
Чтобы гарантированно оказались видны ты обязан использовать lock. твой sleep(10000) вообще ничего не гарантирует.

Ещё как вариант (только если ты хочешь избежать синхронизации использованием lock-free для улучшение перформанса) — объявить все меняемые данные как volatile, но это невозможно для элементов массива, поэтому если только смотреть в сторону AtomicIntegerArray.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[10]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 15:33
Оценка:
Здравствуйте, ., Вы писали:

PD>>Тогда возвращаемся к исходному вопросу в моем примере. Как мне гарантировать, что изменения в классе X, которые он сделал после изменения volatile переменной, будут видны T2 ?

PD>>Более того, как мне гарантировать, что изменения в классе X, которые он сделал, в случае , если он вообще не менял саму ссылку c , будут видны потоку T2 ?
.>Залочить оба треда на одном локе перед изменением и перед чтением этих данных.
.>При освобождении лока пишуший тред сбросит все happens-before данные в главную память и читающий тред при взятии лока прочтёт их из главной памяти.

Вот это понятно. Логично. Если лочить код, исполняемый потоком, то при выходе из лока он может сбросить кеш в ОП. Аналоги в Win32 — Release(Mutex, Semaphor), LeaveCriticalSection, SetEvent...

То есть объявление synchronized ссылки для этой памяти ни чего в этом плане не дает ? Здесь же нет кода. Доступ к этой переменной был давным-давно, изменения вносились после доступа.
With best regards
Pavel Dvorkin
Re[20]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 15:38
Оценка:
Здравствуйте, ., Вы писали:

.>Без локов — могут не оказаться!

.>Чтобы гарантированно оказались видны ты обязан использовать lock. твой sleep(10000) вообще ничего не гарантирует.

Вот теперь ясно. syncronized переменная ничего не гарантирует в этом плане, нужен лок. Не для блокировки чего-то, а для сброса кеша при выходе из него.

.>Ещё как вариант (только если ты хочешь избежать синхронизации использованием lock-free для улучшение перформанса) — объявить все меняемые данные как volatile, но это невозможно для элементов массива, поэтому если только смотреть в сторону AtomicIntegerArray.


Ну массив здесь только для примера. А если это ссылка не на массив, а на развесистое дерево (граф) экземпляров классов ? Например, ссылка на экземпляр класса, в котором мэп из бог знает чего, а это бог знает что содержит мэп бог знает чего еще раз. Не могу же я все лочить, да и нет у меня доступа к внутренним полям этих мэпов.

(Про мэпы сказал просто для примера, не надо рассказывать по конкурентные мэпы
With best regards
Pavel Dvorkin
Re[11]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 15:38
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>То есть объявление synchronized ссылки для этой памяти ни чего в этом плане не дает ? Здесь же нет кода. Доступ к этой переменной был давным-давно, изменения вносились после доступа.

"synchronized"? Не понял. Код давай. syncrhronized и есть Aquire/Release пара.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[12]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 15:45
Оценка:
Здравствуйте, ., Вы писали:

.>Здравствуйте, Pavel Dvorkin, Вы писали:


PD>>То есть объявление synchronized ссылки для этой памяти ни чего в этом плане не дает ? Здесь же нет кода. Доступ к этой переменной был давным-давно, изменения вносились после доступа.

.>"synchronized"? Не понял. Код давай. syncrhronized и есть Aquire/Release пара.

Держи


class A
{
  synchronized int[] a = new int[1000];
  int s;
  void main()
{
 Thread2.start();
 f();
 Thread2.join();
}

  void f() // вызывается из mainThread
{ 
 for (int i = 0; i < 1000; i++)
  a[i] = i;

// поток mainThread изменил память, но, возможно, эти изменения только в его кеше, а не в RAM.
}

int sum() // вызывается из Thread2
{
 Thread.sleep(10000);
 // поток Thread2 обращается к этой памяти, но, возможно, ее содержимое неверно.
 for (int i = 0; i < 1000; i++)
  s+=i;
}


synchronized ссылка есть. Менять ее никто не меняет. Меняют память по ней. Код, меняющий память, не запрашивает лока явно.
With best regards
Pavel Dvorkin
Re[13]: поправка
От: Pavel Dvorkin Россия  
Дата: 14.02.12 15:46
Оценка:
Вместо

PD> for (int i = 0; i < 1000; i++)

PD> s+=i;

читать

PD> for (int i = 0; i < 1000; i++)

PD> s+=a[i];
With best regards
Pavel Dvorkin
Re[21]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 15:46
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

.>>Без локов — могут не оказаться!

.>>Чтобы гарантированно оказались видны ты обязан использовать lock. твой sleep(10000) вообще ничего не гарантирует.
PD>Вот теперь ясно. syncronized переменная ничего не гарантирует в этом плане, нужен лок. Не для блокировки чего-то, а для сброса кеша при выходе из него.
synchronized(obj) {
 f();
}

логически эквивалентно такому:
obj.$monitor.lock();
try {
  f();
}
finally {
  obj.$monitor.unlock();
}

, где ($monitor — скрытая структура JVM которая есть у каждого объекта в яве).
т.е. я не понял твой вопрос, т.к. ты видимо не понимаешь что делает synchronized. или ты хотел сказать volatile?

PD>Ну массив здесь только для примера. А если это ссылка не на массив, а на развесистое дерево (граф) экземпляров классов ? Например, ссылка на экземпляр класса, в котором мэп из бог знает чего, а это бог знает что содержит мэп бог знает чего еще раз. Не могу же я все лочить, да и нет у меня доступа к внутренним полям этих мэпов.

PD>(Про мэпы сказал просто для примера, не надо рассказывать по конкурентные мэпы
Ты можешь лочить все обращения к этой развесистой структуре.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[13]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 15:48
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Держи

PD>
PD>  synchronized int[] a = new int[1000];
PD>

Это оффтопик, т.к. не ява.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[22]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 15:48
Оценка:
Здравствуйте, ., Вы писали:

.>
.>synchronized(obj) {
.> f();
.>}
.>

.>логически эквивалентно такому:
.>
.>obj.$monitor.lock();
.>try {
.>  f();
.>}
.>finally {
.>  obj.$monitor.unlock();
.>}
.>

.>, где ($monitor — скрытая структура JVM которая есть у каждого объекта в яве).

С этим не спорю. Ответ дан в предыдущем моем письме, там и продолжим, если хочешь.
With best regards
Pavel Dvorkin
Re[14]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 15:50
Оценка:
Здравствуйте, ., Вы писали:

.>Здравствуйте, Pavel Dvorkin, Вы писали:


PD>>Держи

PD>>
PD>>  synchronized int[] a = new int[1000];
PD>>

.>Это оффтопик, т.к. не ява.

Тьфу. Рехнулся я с этими synchronized

volatile int[] a = new int[1000];
With best regards
Pavel Dvorkin
Re[9]: вопрос по блокировке
От: StanislavK Великобритания  
Дата: 14.02.12 15:53
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Поток Т1 записал в volatile, его память переписана в RAM. Он потом делал изменения по этой ссылке в классе X, но не менял саму ссылку (то есть не менял volatile переменную, а менял данные, по ней доступные по ссылке). Поток T2 обратился к этим данным и увидел то, что было до этих изменений. (может, конечно, и изменения увидел, но не гарантируется)

PD>Я ничего не переврал ? Вроде нет.
Ага, так.

PD>Тогда возвращаемся к исходному вопросу в моем примере. Как мне гарантировать, что изменения в классе X, которые он сделал после изменения volatile переменной, будут видны T2 ?

PD>Более того, как мне гарантировать, что изменения в классе X, которые он сделал, в случае , если он вообще не менял саму ссылку c , будут видны потоку T2 ?
Это вот этот?

class A
{
  int[] a = new int[1000];
  int s;

void f() // вызывается из mainThread
{ 
 for (int i = 0; i < 1000; i++)
  a[i] = i;
 Thread2.start();
}

int sum() // вызывается из Thread2
{
 for (int i = 0; i < 1000; i++)
  s+=i;
}


Не совсем очевидно, но тут все будет хорошо. Thread.start имеет happens-before семантику — "память потока" потока, который вызывает Thread2.start() будет "записана" в общую память. Про это тут: http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/package-summary.html "Memory Consistency Properties". Но в общем, в таких моментах рекомендуется делать синхронизацию, исключительно из сообращений "читабельности".

PD>>>Так ?

SK>>Не уверен. Не понимаю при чем тут все ядра.
PD>Попробуй ответить на мой вопрос, поймешь
Все равно не понял.
Re[15]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 15:54
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Тьфу. Рехнулся я с этими synchronized

Тихо шифером шурша...

PD> volatile int[] a = new int[1000];

Тогда этот код может глючить, не потокобезопасный. А volatile тут ни на что не влияет. И всё в точности как и в С++.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[10]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 16:02
Оценка:
Здравствуйте, StanislavK, Вы писали:

SK>Здравствуйте, Pavel Dvorkin, Вы писали:


PD>>Поток Т1 записал в volatile, его память переписана в RAM. Он потом делал изменения по этой ссылке в классе X, но не менял саму ссылку (то есть не менял volatile переменную, а менял данные, по ней доступные по ссылке). Поток T2 обратился к этим данным и увидел то, что было до этих изменений. (может, конечно, и изменения увидел, но не гарантируется)

PD>>Я ничего не переврал ? Вроде нет.
SK>Ага, так.

PD>>Тогда возвращаемся к исходному вопросу в моем примере. Как мне гарантировать, что изменения в классе X, которые он сделал после изменения volatile переменной, будут видны T2 ?

PD>>Более того, как мне гарантировать, что изменения в классе X, которые он сделал, в случае , если он вообще не менял саму ссылку c , будут видны потоку T2 ?
SK>Это вот этот?

Лучше этот


class A
{
  volatile int[] a = new int[1000]; 
  int s;
  void main()
{
 Thread2.start();
 f();
 Thread2.join();
}

  void f() // вызывается из mainThread
{ 
 for (int i = 0; i < 1000; i++)
  a[i] = i;
}

int sum() // вызывается из Thread2
{
 Thread.sleep(10000);
 for (int i = 0; i < 1000; i++)
  s+=i;
}


Здесь Thread.start вызывается намного раньше всех этих действий. Поток main расписывает массив, поток Thread2 хочет брать оттуда данные. synchronized нет, volatile есть, но она тут ничего не делает, так как эта ссылка не меняется.

Данные будут корректные ?
With best regards
Pavel Dvorkin
Re[16]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 16:03
Оценка:
Здравствуйте, ., Вы писали:

.>Тогда этот код может глючить, не потокобезопасный. А volatile тут ни на что не влияет. И всё в точности как и в С++.


Резюме — volatile в плане синхронизации данных по этой ссылке ничего не дает, а дает только атомарность изменения самой ссылки. synchronized и все его аналоги дают. Так ?
With best regards
Pavel Dvorkin
Re[11]: вопрос по блокировке
От: StanislavK Великобритания  
Дата: 14.02.12 16:16
Оценка: 18 (1)
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Здравствуйте, StanislavK, Вы писали:


PD>Лучше этот

PD>
PD>class A
PD>{
PD>  volatile int[] a = new int[1000]; 
PD>  int s;
PD>  void main()
PD>{
PD> Thread2.start();
PD> f();
PD> Thread2.join();
PD>}

PD>  void f() // вызывается из mainThread
PD>{ 
PD> for (int i = 0; i < 1000; i++)
PD>  a[i] = i;
PD>}

PD>int sum() // вызывается из Thread2
PD>{
PD> Thread.sleep(10000);
PD> for (int i = 0; i < 1000; i++)
PD>  s+=i;
PD>}
PD>


PD>Здесь Thread.start вызывается намного раньше всех этих действий. Поток main расписывает массив, поток Thread2 хочет брать оттуда данные. synchronized нет, volatile есть, но она тут ничего не делает, так как эта ссылка не меняется.

PD>Данные будут корректные ?
Если внимательно посмотреть, то тут все будет пучком, т.к. потоки не шарят данные
Подозреваю, что "s+=i", это на самом деле "s+=a[i]". В этом случае без дополнительной синхронизации никаких гарантий нет.
Re[17]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 16:16
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

.>>Тогда этот код может глючить, не потокобезопасный. А volatile тут ни на что не влияет. И всё в точности как и в С++.

PD>Резюме — volatile в плане синхронизации данных по этой ссылке ничего не дает, а дает только атомарность изменения самой ссылки. synchronized и все его аналоги дают. Так ?
Не даёт. Атомарность тут не причём (все ссылки и так атомарны). synchronized дают, ценой ухудшения производительности.

Разница volatile/non-volatile для данного случая проявится например в такой ситуации:
class C
{
  /*volatile*/ int []a;
  void f()
  {
    int []a = new int[1000];
    for (int i = 0; i < 1000; i++)
     a[i] = i;
    this.a = a;
  }
}

Тут наличие volatile — обязательно для консистентности данных.

ЗЫЖ У меня появился вопрос к знатокам. В коде, который привёл Павел, второй тред гарантировано увидит 999 элементов, т.к. для записи i-го элемента берётся ссылка на массив из volatile переменной. Значит при чтении этой переменной он должен записать в главную память предыдущий элемент. Последний элемент же может остаться в кеше, т.к. после его записи не происходит обращений к volatile-переменным. Я правильно понимаю? Или чтение volatile-переменной не гарантирует happens-before запись ячейки массива?
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[12]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 14.02.12 16:19
Оценка:
Здравствуйте, StanislavK, Вы писали:

SK>Подозреваю, что "s+=i", это на самом деле "s+=a[i]".


Да, конечно.

>В этом случае без дополнительной синхронизации никаких гарантий нет.


Ясно. А для нее достаточно войти в какой-нибудь лок, тогда при выходе из него Thread2 сбросит кэш.

Спасибо.
With best regards
Pavel Dvorkin
Re[13]: вопрос по блокировке
От: . Великобритания  
Дата: 14.02.12 16:24
Оценка: 18 (1) +1
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Ясно. А для нее достаточно войти в какой-нибудь лок, тогда при выходе из него Thread2 сбросит кэш.

Не достаточно! Ещё необходимо, чтобы другой тред тоже вошел в тот же лок, чтобы он перегрузил свой кеш из сброшенного в главную память другого кеша.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[13]: вопрос по блокировке
От: StanislavK Великобритания  
Дата: 14.02.12 16:38
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

SK>>Подозреваю, что "s+=i", это на самом деле "s+=a[i]".

PD>Да, конечно.

>>В этом случае без дополнительной синхронизации никаких гарантий нет.

PD>Ясно. А для нее достаточно войти в какой-нибудь лок, тогда при выходе из него Thread2 сбросит кэш.
лушче покажите на примере кода, я так не понимаю. слишком много возможностей для интерпретации этой фразы.
Re: вопрос по блокировке
От: E.K. Великобритания  
Дата: 14.02.12 17:08
Оценка: 18 (1)
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Хотелось бы знать мнение по этому вопросу, как лучше сделать.


Вот смотрю я на весь этот тред и вижу несколько вещей:

1) вы упорно пытаетесь все свести к модели C++ хотя и языки и система поддержки многопоточности и платформы разные. Программы написанные на Java исполняются на виртуальной машине а не голом железе — о каких ядрах и проч. тут может идти речь? Виртуальная машина вправе реализовывать кеш потока так как ей вздумается — используя внутренние словари, функции ОС и т.п. Java вам может дать лишь определенные гарантии относительно порядка выполнения операций и видимости изменений но не говорит как все это должно быть реализовано.

2) Применительно к вашей задаче уже много разных людей дали по сути один и тот же ответ только в разной форме. Причем по несколько раз. Если вам просто задачу решить, так это уже много раз показали как это сделать, с примерами кода. Если вам важнее разобраться, понять какова модель памяти в Java, тут нужно курить доки и спеки, посколько очевидно на пальцах объяснить не получается. Пока не будет твердого понимания что такое memory consistency, happens-before, syncrhonization-order, total-order vs. local-order, а также как на них влияют применение volatile, syncrhonized и др. примитивов синхронизации тут можно долго и бесполезно спорить. Нужно читать.

Ссылки уже давали, приведу опять:

Если нужна помощь с переводом — обращайтесь.
It is always bad to give advices, but you will be never forgiven for a good one.
Oscar Wilde
Re[14]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 15.02.12 03:13
Оценка:
Здравствуйте, ., Вы писали:

.>Здравствуйте, Pavel Dvorkin, Вы писали:


PD>>Ясно. А для нее достаточно войти в какой-нибудь лок, тогда при выходе из него Thread2 сбросит кэш.

.>Не достаточно! Ещё необходимо, чтобы другой тред тоже вошел в тот же лок, чтобы он перегрузил свой кеш из сброшенного в главную память другого кеша.

Да, согласен.

Спасибо.
With best regards
Pavel Dvorkin
Re[2]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 15.02.12 03:23
Оценка:
Здравствуйте, E.K., Вы писали:

EK>1) вы упорно пытаетесь все свести к модели C++ хотя и языки и система поддержки многопоточности и платформы разные.


Это не совсем так. Модели С++ не существует, есть модель подлежащей платформы. Нет никаких различий, писать на С++ или на ассемблере или Delphi (нативной). Меня как раз интересовало, как сопрягается Ява с подлежащей платформой, то есть ОС и работой процессора.


>Программы написанные на Java исполняются на виртуальной машине а не голом железе — о каких ядрах и проч. тут может идти речь?


Ядра никуда не денутся, потому что они есть. И потоки тоже. Работа кеша ядер не зависит от того, есть тут Java VM или .NET VM или что-то иное. С точки зрения Windows Java VM есть просто приложение, вполне обычное (потому что никаких необычных приложений нет), а с точки зрения процессора и ядер даже не важно, работает тут Windows или Linux или еще что-то. Потоки больше зависят от ОС, но в пределах ОС потоки есть потоки, исполняются ли они в Java VM или в моей сделанной на С++ программе. Кстати, Java VM есть всего лишь программа, написанная на С++

>Виртуальная машина вправе реализовывать кеш потока так как ей вздумается — используя внутренние словари, функции ОС и т.п.


Это она может сделать с памятью, которую она сама отведет для потока внутри себя. Над кешем процессора она не властна.


EK>Если нужна помощь с переводом — обращайтесь.


Спасибо, проблем тут нет.
With best regards
Pavel Dvorkin
Re[3]: вопрос по блокировке
От: maxkar  
Дата: 16.02.12 07:48
Оценка: 9 (1)
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Здравствуйте, E.K., Вы писали:


EK>>1) вы упорно пытаетесь все свести к модели C++ хотя и языки и система поддержки многопоточности и платформы разные.


PD>Это не совсем так. Модели С++ не существует, есть модель подлежащей платформы. Нет никаких различий, писать на С++ или на ассемблере или Delphi (нативной). Меня как раз интересовало, как сопрягается Ява с подлежащей платформой, то есть ОС и работой процессора.


Есть разные модели! Есть. Java Memory Model может быть различными способами сопряжена с платформой. Например, volatile не гарантирует сброс кэша. В частности, если вирутальная машина видит, что ссылки на объект никогда не размещались в куче (а только на стеке), то она может пропускать операции "сбороса/чтения" кэша. Называется lock elision (в гугле информация есть). В 6-ке эта техника вроде бы не реализована, но в будущих версиях вполне может быть введена без какого-либо уведомления. Отсюда и требование синхронизации на одном и том же объекте.

>>Программы написанные на Java исполняются на виртуальной машине а не голом железе — о каких ядрах и проч. тут может идти речь?


PD>Работа кеша ядер не зависит от того, есть тут Java VM или .NET VM или что-то иное.

Работа ядер не зависит. А вот модель языка может быть по-разному отображена на операции с кэшем (сброс кэша). Кроме того, Java подразумевает работу на нескольких архитектурах. Например, на NUMA, где понятия "общей памяти" может не быть как таковой. В этом случае обращение к volatile (и любое другое Happens-before отношение) — загрузка выгрузка только изменившихся страниц. Или "синхронизация" рабочей памяти с тем процессором, который в последний раз приобретал блокировку. Отсюда опять требование синхронизации на одном и том же объекте.

И вообще, модель с "записью в общую память" не очень хорошая. Она была правильна до версии 1.4 включительно (и в ее терминах формулировалась модель памяти в JLS 2-nd edition). В третьей версии от понятия "общая память" полностью отказались и перевели все на happens-before. Сама по себе "общая память" гарантирует "монотонность" значений (если мы увидели какое-то значение, мы больше не можем увидеть более старое). Happens-before — не гарантирует (естественно, при остутствии синхронизации). Обсуждали здесь
Автор: maxkar
Дата: 15.10.10


PD>Это она может сделать с памятью, которую она сама отведет для потока внутри себя. Над кешем процессора она не властна.

Ну да. Но ведь вы будете видеть как раз то, что виртуальная машина сделала с памятью потока! А кэши процессора вы видите только "косвенно", как особенность реализации JMM на конкретной платформе.
Re[14]: вопрос по блокировке
От: StanislavK Великобритания  
Дата: 16.02.12 20:13
Оценка:
Здравствуйте, ., Вы писали:

.>Здравствуйте, Pavel Dvorkin, Вы писали:


PD>>Ясно. А для нее достаточно войти в какой-нибудь лок, тогда при выходе из него Thread2 сбросит кэш.

.>Не достаточно! Ещё необходимо, чтобы другой тред тоже вошел в тот же лок, чтобы он перегрузил свой кеш из сброшенного в главную память другого кеша.
Идеологически оно, конечно, верно. Но на практике не правда. Не нужен тот же лок.
Re[5]: вопрос по блокировке
От: StanislavK Великобритания  
Дата: 09.03.12 22:36
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>Здравствуйте, StanislavK, Вы писали:



SK>>Так, что вот.. многопоточность, она на самом деле много хуже, чем кажется и читайте Java Memory Model и JVM spec про потоки, там еще много чего написано.

PD>Да, наверное ты прав. Я с потоками имел дело не раз на С++. Там нет необходимости учитывать этот memory barrier, так как примитивы синхронизации Win API обеспечивают запись измененных данных в RAM. Но там нет никакой working memory у потока (хотя кэш процессора, конечно, есть). Если в Яве ввели еще некую локальную память для потока, то понятно, что необходимо обеспечить ее синхронизацию с основной памятью.
PD>Для уточнения — такой вопрос. Пусть я в одном потоке записал элементы некоего 100 Кб массива. Что я должен сделать, чтобы другой поток гарантированно увидел эти данные ?

Нарвался тут в инете на замечательную статью про С++, там все популярно рассказано насчет того для чего нужны барьеры и какие проблемы могут вознукнуть при re-ordering.

http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

Хотя статья про С++, настоятельно рекумендую для прочтения и тем кто пишет на яве, т.к. проблемы все те же.
Кстати, что интересно, я заметил, что программисты на С++ гораздо менее знакомы с правилами хорошего тона многопоточного программирования, чем программисты на яве. Во всяком случае это касается тех с кем я общаюсь, что ни в коей мере не является статистически значимой выборкой Связанно это с тем (опять же по-моим наблюдениям), что на С++ потоков стараются избегать и не без причины.
Re[6]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 10.03.12 02:05
Оценка:
Здравствуйте, StanislavK, Вы писали:

SK>Хотя статья про С++, настоятельно рекумендую для прочтения и тем кто пишет на яве, т.к. проблемы все те же.

SK>Кстати, что интересно, я заметил, что программисты на С++ гораздо менее знакомы с правилами хорошего тона многопоточного программирования, чем программисты на яве. Во всяком случае это касается тех с кем я общаюсь, что ни в коей мере не является статистически значимой выборкой Связанно это с тем (опять же по-моим наблюдениям), что на С++ потоков стараются избегать и не без причины.

За ссылку спасибо, посмотрю, а вот утверждение насчет того, что на С++ потоков стараются избегать — странное. Я их неоднократно использовал. Просто там все эти проблемы с memory barrier скрыты в примитивах ОС, ну, а, конечно, никаких эффектов, связанных с работой JVM, нет в природе.
With best regards
Pavel Dvorkin
Re[7]: вопрос по блокировке
От: . Великобритания  
Дата: 10.03.12 11:56
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>За ссылку спасибо, посмотрю, а вот утверждение насчет того, что на С++ потоков стараются избегать — странное. Я их неоднократно использовал. Просто там все эти проблемы с memory barrier скрыты в примитивах ОС, ну, а, конечно, никаких эффектов, связанных с работой JVM, нет в природе.

Да всё то же самое. Только вместо эффектов JVM — эффекты оптимизатора. А проблем меньше, т.к. примитивы ОС, как я помню по твоим другим мессагам, WinAPI, а значит только одна операционка и одна железная платформа. В отличие от многоплатформенной явы.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[8]: вопрос по блокировке
От: Pavel Dvorkin Россия  
Дата: 10.03.12 15:48
Оценка:
Здравствуйте, ., Вы писали:

.>Да всё то же самое. Только вместо эффектов JVM — эффекты оптимизатора. А проблем меньше, т.к. примитивы ОС, как я помню по твоим другим мессагам, WinAPI, а значит только одна операционка и одна железная платформа. В отличие от многоплатформенной явы.


Против этого не возразишь, но для этой одной платформы эффекты скрываются примитивами.
With best regards
Pavel Dvorkin
Re[7]: вопрос по блокировке
От: StanislavK Великобритания  
Дата: 10.03.12 23:52
Оценка:
Здравствуйте, Pavel Dvorkin, Вы писали:

PD>За ссылку спасибо, посмотрю, а вот утверждение насчет того, что на С++ потоков стараются избегать — странное. Я их неоднократно использовал. Просто там все эти проблемы с memory barrier скрыты в примитивах ОС, ну, а, конечно, никаких эффектов, связанных с работой JVM, нет в природе.


Что за эффекты?
Re[6]: вопрос по блокировке
От: Eugeny__ Украина  
Дата: 12.03.12 09:45
Оценка:
Здравствуйте, StanislavK, Вы писали:


SK>Кстати, что интересно, я заметил, что программисты на С++ гораздо менее знакомы с правилами хорошего тона многопоточного программирования, чем программисты на яве. Во всяком случае это касается тех с кем я общаюсь, что ни в коей мере не является статистически значимой выборкой Связанно это с тем (опять же по-моим наблюдениям), что на С++ потоков стараются избегать и не без причины.


Хехе, а ты попробуй запостить это утверждение в КСВ, там тебе быстро(страничек этак на 10) и решительно объяснят, что это неправильные плюсовики, и они пишут неправильный код. И вообще таких не существует.
Новости очень смешные. Зря вы не смотрите. Как будто за наркоманами подсматриваешь. Только тетка с погодой в завязке.
There is no such thing as a winnable war.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.