Не делайте "ничего", пока "условие"

При просмотре кода для версии 8 на Java 8 ForkJoinPool (который имеет несколько интересных изменений от Java 7) я наткнулся на эту конструкцию (здесь):

do {} while (!blocker.isReleasable() &&
             !blocker.block());

Я боюсь, почему вы напишете это так, а не просто

while (!blocker.isReleasable() &&
       !blocker.block());

Это просто выбор семантики/удобочитаемости, поскольку вы можете прочитать первую конструкцию как do "nothing" while "conditions"? Или есть некоторые дополнительные преимущества, которые мне не хватает?

Ответ 1

Если вы читаете комментарии в верхней части файла, чуть ниже объявления класса, есть раздел, который объясняет использование этой конструкции:

Style notes

===========

[...]

There are several occurrences of the unusual "do {} while
(!cas...)"  which is the simplest way to force an update of a
CAS'ed variable. There are also other coding oddities (including
several unnecessary-looking hoisted null checks) that help
some methods perform reasonably even when interpreted (not
compiled).

Ответ 2

ForkJoinPool широко использует compareAndSwap... из sun.misc.Unsafe, и большинство вхождений do {} while (...) в ForkJoinPool может — как упомянуто другими ответами; объясняется этим комментарием в заголовке Заметки стиля:

* There are several occurrences of the unusual "do {} while
* (!cas...)"  which is the simplest way to force an update of a
* CAS'ed variable. 

Выбор использовать запись while -loop с пустым телом как do {} while (condition) представляется, однако, главным стилистическим выбором. Это, пожалуй, яснее в HashMap, которое, как оказалось, обновилось на Java 8.

В Java 7 HashMap вы можете найти это:

while (index < t.length && (next = t[index++]) == null)
    ;

В то время как большая часть кода вокруг него также изменилась, ясно, что замена в Java 8 такова:

do {} while (index < t.length && (next = t[index++]) == null);

В первой версии есть слабость: если одиночная точка с запятой была удалена, она изменила бы смысл программы в зависимости от следующей строки.

Как видно ниже, байт-код, сгенерированный while (...) {} и do {} while (...);, немного отличается, но никак не влияет на что-либо при запуске.

Код Java:

class WhileTest {
    boolean condition;

    void waitWhile() {
        while(!condition);
    }

    void waitDoWhile() {
        do {} while(!condition);
    }
}

Сгенерированный код:

class WhileTest {
  boolean condition;

  WhileTest();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return        

  void waitWhile();
    Code:
       0: aload_0       
       1: getfield      #2                  // Field condition:Z
       4: ifne          10
       7: goto          0
      10: return        

  void waitDoWhile();
    Code:
       0: aload_0       
       1: getfield      #2                  // Field condition:Z
       4: ifeq          0
       7: return        
}

Ответ 3

Оставляя в стороне любые потенциальные преимущества, существует четкая удобочитаемость.

С while (X) ; конечная точка с запятой не всегда очевидна с первого взгляда, вы можете быть смущены, думая, что следующий оператор или утверждения находятся внутри цикла. Например:

while (x==process(y));
if (z=x) {
    // do stuff.
}

Было бы очень легко неверно понять сказанное выше, как иметь оператор if внутри цикла, и даже если бы вы его правильно прочитали, было бы легко подумать, что это ошибка программирования, а if должна находиться внутри цикла.

С do {} while(X);, хотя сразу видно, что тела нет в цикле.

Ответ 4

Если вы прочитаете комментарий выше кода, то упоминается, что...

Если вызывающий объект не является ForkJoinTask, этот метод поведенчески эквивалентен

while (!blocker.isReleasable())
   if (blocker.block())
      return;
}

Так что это просто еще одна форма для реализации выше кода в другой части...!!

В Заметки стиля упоминается, что

Существует несколько случаев необычного "do {}, в то время как  (! cas...)", который является самым простым способом принудительного обновления  Переменная CAS.

И если вы увидите реализацию ManagedLocker # isReleasable, она обновляет блокировку и возвращает true, если блокировка не нужна.

Интерпретация:

Бланк, в то время как циклы используются для обеспечения прерывания до тех пор, пока какое-либо условие reset не вернется /false.

Здесь do { } while(!...) является блокиратором/прерыванием, пока blocker.block() не будет true, когда blocker.isReleasable() будет false. Loop продолжит выполнение, а blocker не освобождается (!blocker.isReleasable()) и blocker не заблокирован!! Выполнение будет вне цикла, как только blocker.block() будет установлено значение true.

Обратите внимание, что do{ } while(...) не обновляет переменную CAS, но гарантирует, что программа будет ждать обновления переменной (принудительно ждать обновления переменной).

Ответ 5

Вы можете легко сделать что-то вроде этого:

if(true){
 //Do nothing ...
}