Могу ли я исключить вложенную синхронизацию в частном поле в синхронизированном методе
У меня есть этот устаревший псевдокод:
public class Wrapper {
private volatile Sink sink;
public synchronized void flushSink() {
if (sink != null) {
synchronized (sink) {
sink.flush();
}
}
}
public void close() throws IOException {
var sink = this.sink;
if (sink != null) {
sink.receivedLast();
}
}
}
Мой вопрос о вложенныхsynchronized
блокировать.
Насколько я понимаю, упомянутый блок в данном случае является избыточным, как если бы два потока одновременно вызывалиflushSink()
, то только один из них имеет доступ к приватному полюsink
вовремя. В результате код может быть упрощен до
public class Wrapper {
private volatile Sink sink;
public synchronized void flushSink() {
if (sink != null) {
sink.flush();
}
}
}
с возможным дальнейшим устранением расизма, зачитанного в
public class Wrapper {
private volatile Sink sink;
public synchronized void flushSink() {
var sink = this.sink;
if (sink != null) {
sink.flush();
}
}
}
Итак, мой вопрос заключается в том, является ли такой рефакторинг правильным с точки зрения синхронизации и JMM, и если это так (как вариант, если это не так) - есть ли какой-либо тест на основе JCStress, подтверждающий это?
2 ответа
Насколько я понимаю, указанный блок в данном случае является избыточным, т.к. если два потока одновременно вызывают flushSink(), то только один из них имеет доступ к приемнику приватного поля одновременно. В результате код может быть упрощен до
Это избыточно до тех пор, пока в коде нет другого места, гдеsink
осуществляется в рамках синхронизированного блока.
Например, еслиclose()
метод был изменен на следующий,
public void close() throws IOException {
var sink = this.sink;
if (sink != null) {
synchronized(sink) {
sink.receivedLast();
}
}
}
затем синхронизированный блок вflushSink()
не было бы бесполезным, так как это гарантировало бы, чтоreceivedLast()
иflush()
не вызываются одновременно.
Наконец я нашел открытый исходный код, точно повторяющий случай, см.java.io.PipedOutputStream
:
@Override
public synchronized void flush() throws IOException {
if (sink != null) {
synchronized (sink) {
sink.notifyAll();
}
}
}
Код выглядит точно так же, как фрагмент, который я исследовал, и если я сделаю то же самое, связанные с рефакторингом тесты JDK потерпят неудачу.
Трансформация незаконна, потому чтоsynchronized
наflush()
метод предполагает блокировкуthis
то есть наPipedOutputStream
объект иsynchronized (sink)
блокирует другой объект. Таким образом, у нас есть две разные блокировки, и огрубление блокировки в этом случае не допускается.