Вдвойне Check Lock Не работает с этим java кодом? - PullRequest
1 голос
/ 30 января 2020

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

Строка get и Increment устанавливает блокировку строки на БД и увеличивает строку, чтобы обеспечить уникальное обновление только на уровне БД. Так что в первом случае проблем нет.

Проблема лежит в другом блозке. Когда идентификатор корреляции не равен нулю, мы пытаемся извлечь уже сопоставленное значение, если это первый вызов. затем мы сначала отображаем значение, а затем возвращаем его. Это сопоставление должно быть синхронизировано, чтобы два разных потока не обновляли следующий параметр, и оба они находят его равным нулю.

Этот класс также является одноэлементным сервисом.

public class RangeQueryService{

private volatile String nextValue=null;


public String getNextIncrement(String name, String correlationId) throws SomeCheckedException {
    try {
            if (correlationId == null) {

                nextValue = rangeFetch.getAndIncrementAsString(name);

            } else { //Enter Sync branch

                // mapper Will Return null if no value is mapped.

                nextValue = mapper.mapToB(SOME_CONST, correlationId);

                // Avoid syncronization overhead if value is already fetched. Only enter if nextVal is null.

                if (nextValue == null) {

                    synchronized (this) {
                        Doubly Check lock pattern, as two threads can find null simultaneously, and wait on the critical section.

                        if(nextValue==null){
                            nextValue = rangeFetch.getAndIncrementAsString(name);
                            idMapper.mapToB(SOME_CONST, correlationId, nextValue, DURATION);
                        }
                    }
                }
            }

        return nextValue;
    } catch (Exception e) {
        throw new SomeCheckedException("Error!" + e.getMessage());
    }
}

Возвращает 19 и 20. Должно возвращаться только 19.

Вывод:

headerAfterProcessOne: 0000000019, headerAfterProcessTwo: 0000000020

1 Ответ

0 голосов
/ 30 января 2020

Если я вас правильно понял, вы ожидаете, что один поток (давайте назовем его A ) будет ожидать другого (что увеличивает значение до 19, B ), а затем пропустите приращение, потому что nextValue равно 19 и не равно нулю. Но изменение невидимо ожидающим потоком.

Возможный сценарий намного сложнее, как я вижу:

Проблема в том, что 19 возвращается A * Поток 1013 *, который ожидает, поскольку он сразу пропускает весь блок после того, как переменное значение будет опубликовано потоком B в строке:

nextValue = rangeFetch.getAndIncrementAsString(name);

В другом случае поток A входит в метод, а nextValue уже опубликован.

Таким образом, он немедленно переходит к оператору return и return 19, который был установлен B тема (Да, это неожиданно, но иногда это случается). Не следует ожидать, что поток A будет ожидать завершения выполнения B до sh. B (который первым достиг синхронизированного блока) завершает sh обработку (приращение) значения и возвращает 20.

Однако для этого есть возможные обходные пути:

if (nextValue == null) {
    synchronized(this) {
        if(nextValue == null) {
            String localTemp = rangeFetch.getAndIncrementAsString(name);
            idMapper.mapToB(SOME_CONST, correlationId, localTemp, DURATION);
            nextValue = localTemp;
        }
    }
}

Суть в том, что изменения, внесенные в nextValue немедленно , влияют на другие вызовы getNextIncrement.

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

...