Почему 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
,