Предположим, что читатели = 10 х писатели, какое из следующих решений лучше с точки зрения пропускной способности. Какое решение лучше использовать в производственном коде?
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public final class InstanceProvider {
private final Lock wLock = new ReentrantLock();
private volatile List<Instance> instances;
public void setInstances(List<Instance> newInstances) {
wLock.lock();
try {
doSmthWith(newInstances);
instances = newInstances;
} finally {
wLock.unlock();
}
}
public List<Instance> getInstances() {
return instances;
}
@Getter
public static final class Instance {
private final String name;
public Instance(String name) {
this.name = name;
}
}
}
import java.util.List;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public final class InstanceProvider {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private List<Instance> instances;
public void setInstances(List<Instance> newInstances) {
lock.writeLock().lock();
try {
doSmthWith(newInstances);
instances = newInstances;
} finally {
lock.writeLock().unlock();
}
}
public List<Instance> getInstances() {
lock.readLock().lock();
try {
return instances;
} finally {
lock.readLock().unlock();
}
}
@Getter
public static final class Instance {
private final String name;
public Instance(String name) {
this.name = name;
}
}
}
Я предполагаю, что чтение энергозависимых данных обходится дешевле (в основном они будут кэшироваться большую часть времени), а блокировки требуют выполнения дополнительных инструкций.
Что делает doSmthWith()? Изменяет ли это список? Зачем нужна синхронизация?
В этом коде есть фундаментальная проблема: ничто не гарантирует неизменяемость списка. У вызывающего абонента setInstances есть ссылка на список, как и у каждого вызывающего абонента getInstances(). Каждый мог изменить список и, следовательно, все сломать. Называть этот класс также ошибочно InstanceProvider; он не предоставляет экземпляры, он просто поддерживает список экземпляров, предоставленный кем-то другим.




Блокировка записи также не требуется. Если это буквально так и вас не волнует порядок, просто создайте это поле volatile и вообще не используйте никаких блокировок. Это работает, потому что Java гарантирует, что присвоение ссылок является атомарным.
Вы должны использовать блокировки, когда есть несколько шагов (или один шаг, который не гарантирует атомарность), и вы не хотите, чтобы кто-либо когда-либо читал что-то недоделанное (между шагами).
Вот почему здесь вы «уходите» вообще без блокировки. Вы просто хотите убедиться, что событие «прежде чем» не отображается и не означает, что один поток может писать в это поле, а другим потокам требуется день, чтобы «увидеть» это обновление, что JMM позволяет JVM сделать, если вы также удалите volatile. Так что держите это при себе. Блокировка все равно может произойти — дело в том, что решает JVM, а не вы. volatile гарантирует, что запись в это поле и чтение в это поле будут установлены до того, как вы «увидите» это.
Смысл блокировки чтения/записи заключается в том, что несколько объектов могут читать одновременно, не блокируя друг друга, но блокировка записи блокирует все остальные записи и все другие чтения (и блокируется любым открытым чтением). Здесь вам ничего этого не нужно.
Блокировка полезна в нескольких случаях:
Сохранение атомарности операций. Например, операция приращения технически состоит из чтения, обновления и записи. Если другой поток может получить доступ к вашему полю между чтением и записью, возникает состояние гонки, которое может привести к повреждению данных.
Обеспечение видимости обновлений. Когда вы изменяете поле, нет никакой гарантии, что другой поток увидит эти изменения, если только он не синхронизируется с вашим.
Безопасная публикация. Когда вы назначаете объект полю, если только объект не является неизменяемым, другой поток может видеть его в частично созданном состоянии. Как и в случае с проблемой видимости, этого можно избежать с помощью синхронизации.
В вашем сценарии ни одна из этих проблем не имеет значения. Операция записи (setInstances()) обновляет только ссылочное поле, которое по своей сути является атомарной операцией. Видимость и безопасная публикация обеспечиваются за счет создания поля volatile, которое гарантирует то, что записи сразу и полностью видны при последующих чтениях.
Так что запирать вообще не надо. Просто ставьте volatile.
Блокировка ничего не дает;
volatile— это все, что вам здесь нужно.