Могу ли я исключить вложенную синхронизацию в частном поле в синхронизированном методе

У меня есть этот устаревший псевдокод:

      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)блокирует другой объект. Таким образом, у нас есть две разные блокировки, и огрубление блокировки в этом случае не допускается.

Другие вопросы по тегам