Почему ThreadLocalRandom реализован так странно?

Этот вопрос касается реализации ThreadLocalRandom в OpenJDK версии 1.8.0.

ThreadLocalRandom предоставляет генератор случайных чисел для каждого потока без накладных расходов на синхронизацию, налагаемых функцией Random. Наиболее очевидной реализацией (IMO) будет что-то вроде этого, которая, похоже, сохраняет обратную совместимость без особых сложностей:

public class ThreadLocalRandom extends Random {
    private static final ThreadLocal<ThreadLocalRandom> tl =
        ThreadLocal.withInitial(ThreadLocalRandom::new);
    public static ThreadLocalRandom current() {
        return tl.get();
    }
    // Random methods moved here without synchronization
    // stream methods here
}

public class Random {
    private ThreadLocalRandom delegate = new ThreadLocalRandom();
    // methods synchronize and delegate for backward compatibility
}

Однако фактическая реализация совершенно иная и довольно странная:

  • ThreadLocalRandom дублирует некоторые методы в Random дословно и другие с небольшими изменениями; несомненно, большая часть этого кода могла быть использована повторно.
  • Thread хранит семя и переменную зонда, используемые для инициализации `ThreadLocalRandom, нарушая инкапсуляцию;
  • ThreadLocalRandom использования Unsafe чтобы получить доступ к переменным в ThreadЯ полагаю, это потому, что два класса находятся в разных пакетах, но переменные состояния должны быть частными в Thread - Unsafe необходим только из-за нарушения инкапсуляции;
  • ThreadLocalRandom хранит его рядом nextGaussian в статике ThreadLocal а не в переменной экземпляра как Random делает.

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

1 ответ

Решение

Основная проблема в том, что большая часть кода является устаревшей и не может (легко) быть изменена - Random был разработан, чтобы быть "потокобезопасным" путем синхронизации всех его методов. Это работает, в тех случаях, когда Random может использоваться в нескольких потоках, но это серьезное узкое место, поскольку никакие два потока не могут одновременно получать случайные данные. Простым решением было бы построить ThreadLocal<Random> объект, таким образом избегая конфликта блокировки, однако это все еще не идеально. Там еще некоторые накладные расходы synchronized методы, даже когда не оспаривается, и построение п Random случаи расточительны, когда все они по сути делают одну и ту же работу.

Так что на высоком уровне ThreadLocalRandom существует как оптимизация производительности, поэтому имеет смысл, что ее реализация будет "странной", поскольку разработчики JDK потратили время на ее оптимизацию.

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

Эффективная Java: пункт 55 также решает эту проблему - ключевой момент заключается в том, что эксперты должны проводить оптимизацию в крайнем случае. Разработчики JDK - те эксперты.

На ваши конкретные вопросы:

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

К сожалению нет, так как методы на Random являются synchronized, Если они были вызваны ThreadLocalRandom будет тянуть в Random Лок-спорная проблема. TLR необходимо переопределить каждый метод, чтобы удалить synchronized Ключевое слово из методов.

Thread хранит семя и переменную зонда, используемые для инициализации ThreadLocalRandom нарушая инкапсуляцию;

Во-первых, это действительно не "нарушение инкапсуляции", так как поле все еще остается закрытым для пакета. Он заключен в капсулу от пользователей, что является целью. Я бы не стал зацикливаться на этом, так как здесь были приняты решения для повышения производительности. Иногда производительность достигается за счет нормального хорошего дизайна. На практике это "нарушение" не обнаружено. Поведение просто заключено в два класса вместо одного.

Положить семена внутрь Thread позволяет ThreadLocalRandom быть полностью без гражданства (кроме initialized поле, которое в значительной степени не имеет значения), и, следовательно, только один экземпляр должен существовать во всем приложении.

ThreadLocalRandom использования Unsafe чтобы получить доступ к переменным в ThreadЯ полагаю, это потому, что два класса находятся в разных пакетах, но переменные состояния должны быть частными в Thread - Unsafe необходим только из-за нарушения инкапсуляции;

Многие JDK классы используют Unsafe, Это инструмент, а не грех. Опять же, я бы не стал слишком переживать по этому поводу. Класс называется Unsafe отговорить непрофессионалов от злоупотребления им. Мы верим / надеемся, что авторы JDK достаточно умны, чтобы знать, когда это безопасно использовать.

ThreadLocalRandom хранит его рядом nextGaussian в статике ThreadLocal а не в переменной экземпляра как Random делает.

Так как когда-либо будет только один случай ThreadLocalRandom для этого не нужно быть переменной экземпляра. Я полагаю, что вы могли бы альтернативно обосновать, что нет необходимости static либо, но в этот момент вы просто обсуждаете стиль. Как минимум, делая это static более ясно оставляет класс по существу без гражданства. Как указано в файле, это поле не является обязательным, но обеспечивает поведение, аналогичное Random,

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