В каких ситуациях do-while может быть более эффективным, чем while?

Пока и делать-пока

Пока и do-while функционально эквивалентны, когда блоки пусты, хотя while кажется более естественным:

do {} while (keepLooping());
while (keepLooping()) {}

Одним из типичных случаев использования while/do-while с пустым блоком является принудительное обновление атомарных объектов с помощью compareAndSet (CAS). Например, приведенный ниже код будет увеличивать a потокобезопасным способом:

int i;
AtomicInteger a = new AtomicInteger();
while (!a.compareAndSet(i = a.get(), i + 1)) {}

Контекст

Несколько частей java.util.concurrent используют идиому do {} while (...) для операций CAS, и javadoc ForkJoinPool объясняет:

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

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

Вопрос

Существуют ли ситуации, когда do {} while (!cas) может быть более эффективным, чем while (!cas) {}, и по каким причинам?


person assylias    schedule 08.05.2013    source источник
comment
Может быть, это просто историческое предпочтение. В любом случае, зачем вообще использовать {}---while (condition); вот что я написал бы.   -  person Marko Topolnik    schedule 08.05.2013
comment
@MarkoTopolnik Согласен с ;. Я нашел это видео, где Дуг Ли говорит около 57:00 минуты: Не используйте while, используйте do-while из-за точек безопасности, а на слайде упоминается меньшее окно гонки. Я предполагаю, что он ссылается на точки сохранения GC, хотя я не вижу, как это имеет значение здесь.   -  person assylias    schedule 08.05.2013
comment
Дуг довольно туманно об этом говорит :) Лучший способ, которым я могу это интерпретировать, - это не do-while против while, а использование тела цикла для присваивания вместо того, чтобы втискивать все в условие.   -  person Marko Topolnik    schedule 08.05.2013
comment
@MarkoTopolnik Я тоже это понимаю, но тогда в чем проблема? Я думаю, что SO - не лучшее место, чтобы спрашивать!   -  person assylias    schedule 08.05.2013
comment
Да ... это неконструктивно для SO, потому что только около трех человек на Земле могут дать ответ, выходящий за рамки предположений :)   -  person Marko Topolnik    schedule 08.05.2013
comment
Циклы do/while могут быть полезны, когда ваш код должен повторить цикл хотя бы один раз и/или когда ваш цикл инициализирует переменную, проверенную внутри условия while.   -  person Shadow Man    schedule 30.05.2013
comment
Другие причины, почему это неконструктивно, заключаются в том, что 1) ответ, скорее всего, зависит от версии Java, которую вы используете, и 2) даже если разница есть, она вряд ли будет существенной в подавляющем большинстве случаев.   -  person Stephen C    schedule 09.10.2014


Ответы (3)


Таким образом, «do while» означает, что он запустит код в цикле while один раз. Затем он запускает код внутри цикла while только в том случае, если условие истинно.

Простая демонстрация

boolean condition = false;

do{
  System.out.Println("this text displayed");
}while(condition == true);

Вывод "этот текст отображается"

Обычный

while(condition == true){
 System.out.Println("this text displayed");
}

выход ""

  • * вывод не отображается из-за того, что условие ложно.

Почему или где вы могли бы использовать do while, я не сталкивался с необходимостью, поэтому я не могу вам помочь. Это просто вопрос определения проблемы/потребности и использования того, что вы знаете, для ее решения. Подобно лего – механический вид, а не «блоки».

person kyle england    schedule 09.10.2014

Могут быть ситуации, когда вычисление ожидаемого и обновленного значений слишком сложно для чтения в той же строке, что и при вызове compareAndSet. Затем вы можете сделать его более читаемым внутри do:

do {
  int expect = a.get();
  int update = expect + 1;
} while (!a.compareAndSet(expect, update));
person oe.elvik    schedule 08.05.2013
comment
Вопрос предполагает пустые блоки, так что while и do-while строго эквивалентны. - person assylias; 08.05.2013
comment
Ага. Мэйби, это больше похоже на это - person oe.elvik; 08.05.2013
comment
См. реальный пример: grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/ — я не думаю, что проблема в этом. - person assylias; 08.05.2013
comment
В большинстве случаев я согласен с тем, что while (cas); это лучше. Я показываю только пример ситуаций, когда do {} while (!cas) может быть более читабельным, чем while (!cas) {}. Возможно, именно поэтому do {} while чаще встречается в документации. - person oe.elvik; 08.05.2013
comment
Ваш код не будет компилироваться, так как expect и update выходят за рамки, как только вы покинете тело цикла. Однако объявление двух переменных перед циклом будет работать, поэтому ваша точка зрения по-прежнему верна. - person siegi; 12.07.2014

Это не вопрос эффективности. Некоторые случаи просто не могут быть решены без do{}while(). Посмотрите на java.util.Random.next (целые биты). Если вы попытаетесь сделать то же самое с помощью while(){}, у вас будет дубликат кода, потому что тело цикла должно быть выполнено один раз перед условием.

Я уже задавал очень похожий вопрос: компиляция циклов в Java.

Этот код:

public class Test {

    static int i = 0;

    public static void main(String[] args) {
        method1();
        method2();
    }

    public static void method2() {
        do{}while(++i < 5);
    }

    public static void method1() {
        while(++i < 5);
    }
}

компилируется в:

public static void method2();
  Code:
   0:   getstatic       #4; //Field i:I
   3:   iconst_1
   4:   iadd
   5:   dup
   6:   putstatic       #4; //Field i:I
   9:   iconst_5
   10:  if_icmplt       0
   13:  return

public static void method1();
  Code:
   0:   getstatic       #4; //Field i:I
   3:   iconst_1
   4:   iadd
   5:   dup
   6:   putstatic       #4; //Field i:I
   9:   iconst_5
   10:  if_icmpge       16
   13:  goto    0
   16:  return

Вы можете заметить дополнительную инструкцию в строке 13 в методе method1(). Но, как было предложено ответом на мой вопрос, это не имеет никакого значения при компиляции JIT в машинные инструкции. Очень неуловимое улучшение производительности. В любом случае, чтобы доказать это, вы должны запустить с ключом PrintAssembly. Теоретически метод2 быстрее, но на практике они должны быть равны.

person Mikhail    schedule 08.05.2013
comment
Вы недостаточно внимательно прочитали вопрос. Тела цикла вообще нет, и вопрос эффективности очень низкоуровневый, касающийся возможного упорядочения нативного кода. - person Marko Topolnik; 08.05.2013
comment
Я только что посмотрел на сборку, и меня ничего не поражает. - person assylias; 08.05.2013
comment
Что ты имеешь в виду? Это все равно? - person Mikhail; 08.05.2013
comment
Только две инструкции появляются в другом порядке, остальные строго идентичны. - person assylias; 09.05.2013
comment
Они не делают того, что вы думаете, что они делают. method1 iterates four times, method2 iterates once. Так что этот результат вполне нормальный и вы сравниваете не те вещи - person smttsp; 30.05.2013
comment
В этом случае тело цикла пустое, поэтому оно не имеет смысла. Состояние - это главное. - person Mikhail; 30.05.2013
comment
Почему люди думают, что дизассемблированные байт-коды поучительны? 1) Мы все знаем, что означают while и do while. 2) Вы не можете сделать вывод об эффективности кода из байт-кодов. JIT-компилятор — это место, где происходит значительная оптимизация. (Даже методы с идентичными последовательностями байт-кода могут работать по-разному... из-за того, как JIT-компилятор использует статистику времени выполнения от интерпретатора.) - person Stephen C; 09.10.2014