Читаемость малых функций, где не требуется объявление новой переменной - PullRequest
1 голос
/ 03 мая 2019

Мне нужен совет относительно структуры кода для небольших методов.Ниже приведен метод из API Java.Collections.class

private static Random r;    
public static void shuffle(List<?> var0) {
    Random var1 = r;
    if (var1 == null) {
       r = var1 = new Random();
    }
    shuffle(var0, var1);
}

Код можно переписать как

private static Random r;    
public static void shuffle(List<?> var0) {
    if (r == null) {
       r = new Random();
    }
    shuffle(var0, r);
}

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

1 Ответ

2 голосов
/ 08 мая 2019

Исходный исходный код выглядит как

public static void shuffle(List<?> list) {
    Random rnd = r;
    if (rnd == null)
        r = rnd = new Random(); // harmless race.
    shuffle(list, rnd);
}

и указывает на точку позади дизайна.Код использует общее поле r и не является потокобезопасным, но разработан таким образом, что последствия «безвредны».

Очевидно, что вы не должны разрабатывать свое программное обеспечение таким образом.Это зависит от экспертов для определенного программного обеспечения, где они думают, производительность имеет значение.(На самом деле этот метод shuffle может не относиться к этой категории)

Так что для вашей альтернативы

private static Random r;    
public static void shuffle(List<?> var0) {
    if (r == null) {
       r = new Random();
    }
    shuffle(var0, r);
}

последствия безопасности отсутствующего потока не будут безвредными.Тест r == null и последующий вызов shuffle(var0, r) несут различные чтения r, и хотя должны быть только переходы от начального null к инициализированному экземпляру Random, чтение и запись могут восприниматься не по порядку, когдаМеханизм безопасного потока не используется, поэтому, когда происходит параллельная запись, r == null может вычислять до false, тогда как последующие shuffle(var0, r) читают null.

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

Метод shuffle класса Collections читаетзначение r в локальной переменной, чтобы гарантировать, что одно и то же значение будет использоваться тестом rnd == null и вызовом shuffle(list, rnd).Так как это может считывать ссылку на экземпляр Random, созданный другим потоком, он опирается на тот факт, что экземпляр Random сам по себе является потокобезопасным, так как в противном случае при быстром чтении может возникнуть несовместимое состояние объекта.

Все еще возможно, что чтение пропустит значение, ранее записанное другим потоком, или что запись другим потоком произойдет между чтением null и последующей записью ссылки на вновь созданный экземпляр Random.Таким образом, вполне возможно, что несколько потоков будут создавать и использовать различные экземпляры Random здесь, что рассматривается как «безвредные» последствия.

Как уже говорилось, вам не следует копировать этот шаблон.Скорее, решите либо для безопасности потоков, либо без безопасности потоков, и запишите свой код как таковой.

...