Извините, это такой длинный вопрос.
В последнее время я много занимался исследованиями в многопоточном режиме, так как я медленно внедряю его в личный проект. Однако, вероятно, из-за обилия немного неправильных примеров, использование синхронизированных блоков и волатильность в некоторых ситуациях все еще немного неясны для меня.
Мой основной вопрос заключается в следующем: изменения в ссылках и примитивах автоматически волатильны (т.е. выполняются в основной памяти, а не в кеше), когда поток находится внутри синхронизированного блока, или чтение также должно быть синхронизировано для он работает правильно?
- Если это так В чем заключается цель синхронизации простого метода getter? (см. пример 1). Кроме того, ВСЕ изменения отправляются в основную память, пока поток синхронизирован ни на чем? например, если он отправлен для выполнения нагрузок по всему месту внутри синхронизации с очень высоким уровнем, каждое последующее изменение будет сделано в основной памяти и никогда не будет кэшироваться до тех пор, пока оно не будет разблокировано?
- Если не Должно ли изменение должно быть явно внутри синхронизированного блока или может, например, использовать java для использования объекта Lock? (см. пример 3)
- Если либо Связан ли синхронизированный объект с каким-либо образом связанным с ссылкой/примитивом (например, непосредственным объектом, который его содержит)? Могу ли я писать, синхронизируясь на одном объекте и читать с другим, если он в противном случае безопасен? (см. пример 2)
(обратите внимание на следующие примеры, которые я знаю, что синхронизированные методы и синхронизированные (это) недоверчивы и почему, но обсуждение этого вопроса выходит за рамки моего вопроса)
Пример 1:
class Counter{
int count = 0;
public synchronized void increment(){
count++;
}
public int getCount(){
return count;
}
}
В этом примере increment() необходимо синхронизировать, поскольку ++ не является атомной операцией. Таким образом, два потока, увеличивающиеся одновременно, могут приводить к общему увеличению на 1 счета. Первоначальный счетчик должен быть атомарным (например, не длинным/двойным/ссылкой), и это нормально.
Нужно ли синхронизировать getCount() и почему именно? Объяснение, которое я слышал больше всего, заключается в том, что я не буду гарантировать, будет ли возвращенный счет возвратным или последующим приращением. Однако это похоже на объяснение чего-то немного другого, что оказалось не в том месте. Я имею в виду, если бы мне нужно было синхронизировать getCount(), то я до сих пор не вижу никакой гарантии - теперь это не значит, что вы не знаете порядок блокировки, не понимая, происходит ли фактическое чтение до/после фактической записи.
Пример 2:
Является ли следующий пример потокобезопасным, если вы предполагаете, что с помощью обмана, не показанного здесь, ни один из этих методов никогда не будет вызываться одновременно? Будет ли подсчитывать прирост ожидаемым образом, если это делается с использованием случайного метода каждый раз, а затем быть прочитанным правильно или блокировка должна быть одним и тем же объектом? (Кстати, я полностью осознаю, насколько смешным этот пример, но я больше интересуюсь теорией, чем практикой)
class Counter{
private final Object lock1 = new Object();
private final Object lock2 = new Object();
private final Object lock3 = new Object();
int count = 0;
public void increment1(){
synchronized(lock1){
count++;
}
}
public void increment2(){
synchronized(lock2){
count++;
}
}
public int getCount(){
synchronized(lock3){
return count;
}
}
}
Пример 3:
Является ли отношение "происхождение-до" просто концепцией Java, или это реальная вещь, встроенная в JVM? Несмотря на то, что я могу гарантировать, что концептуальное происходит - до отношений для этого следующего примера, достаточно ли java, чтобы забрать его, если он встроен? Я предполагаю, что это не так, но является ли этот пример фактически потокобезопасным? Если его потокобезопасность, а что если getCount() не заблокировал?
class Counter{
private final Lock lock = new Lock();
int count = 0;
public void increment(){
lock.lock();
count++;
lock.unlock();
}
public int getCount(){
lock.lock();
int count = this.count;
lock.unlock();
return count;
}
}