1. Введение
Когда случайные числа упоминаются в Java, многие люди думают о классе Ramdom.Если есть необходимость генерировать случайные числа, большую часть времени Random будет использоваться для генерации случайных чисел.Хотя он использует CAS внутри для достижения, но Его производительность не очень хороша в случае многопоточного параллелизма. После JDK1.7 JDK предлагает лучшее решение Давайте узнаем, почему Ramdom работает медленно? Как это решается?
2.Random
Класс Random — это класс, предоставляемый JDK для генерации случайных чисел.Этот класс не является истинно случайным, а псевдослучайным.Псевдослучайный означает, что генерируемые случайные числа действительно имеют определенные правила, и этот закон проявляется.Период зависит от Плюсы и минусы псевдослучайного алгоритма Вообще говоря, период относительно большой, но его можно предсказать. Мы можем просто использовать Random со следующим кодом:
2.1 Случайный принцип
В Random есть много методов. Здесь мы проанализируем более распространенные методы nextInt() и nextInt(intbound).Первый будет вычислять случайное число в диапазоне int, а второй будет вычислять [0, если мы передадим 10 ., 10) случайное число типа int, слева закрытое, справа открытое. Перед конкретным анализом давайте взглянем на метод построения Random():
Можно видеть, что начальное число типа AtomicLong генерируется в соответствии с начальным значением текущего времени в методе построения, что также является ключом к нашему дальнейшему развитию.
2.1.1 nextInt()
Код в nextInt() выглядит следующим образом:
Этот вызов находится непосредственно внутри следующего () метода, передавая 32, где 32 относится к количеству битов Int.
Здесь в соответствии с текущим значением seed будет вычисляться следующий seed по определенным правилам (псевдослучайным), а затем будет выполняться cas, в случае неудачи cas, будут продолжаться циклы вышеперечисленных операций. Наконец, вернитесь в соответствии с количеством битов, которое нам нужно.
2.1.2 nextInt(int bound)
Код в nextInt(intbound) выглядит следующим образом:
Этот процесс на несколько шагов больше, чем nextInt().Конкретные шаги следующие:
- Во-первых, получите 31-битное случайное число.Обратите внимание, что это 31 бит, что отличается от 32 бит выше, потому что отрицательное случайное число может быть получено в методе nextInt(), а nextInt(intbound) предусматривает, что только [0,bound ) перед случайным числом, то есть это должно быть положительное число, а первый бит int — знаковый, поэтому получается только 31 бит.
- Затем выполните связанную операцию.
- Если привязка является степенью числа 2, непосредственно умножьте данные, полученные на первом шаге, на привязку, а затем сдвиньте вправо на бит 31. Объясните: если привязка равна 4, то умножение на 4 на самом деле является сдвигом влево на 2 бита, тогда на самом деле становится Если оно 33-битное, то, если оно сдвинуто вправо на 31 бит, оно снова станет 2-битным, поэтому диапазон размеров 2-битного int на самом деле [0, 4).
- Если это не степень числа 2, она обрабатывается путем взятия остатка.
2.1.3 Узкое место параллелизма
CAS: видно, что в методе next(int bits) операция CAS выполняется над AtomicLong, и в случае неудачи она будет повторяться в цикле. Когда многие люди видят CAS, потому что он не требует блокировки, они сразу же думают о высокой производительности и высоком уровне параллелизма. Но здесь это стало узким местом нашей многопоточной параллельной производительности.Возможно, что когда у нас есть несколько потоков, выполняющих CAS, только один из них должен выйти из строя, а другие будут продолжать выполнять операции CAS в цикле. больше параллельных потоков, его производительность определенно ниже.
Псевдо-совместное использование: Описание псевдо-совместного использования и строк кэша см.Высокопроизводительный деструктор очередей без блокировки, о котором вы должны знать, строка кэша не обрабатывается для значения в AtomicLong
3.ThreadLocalRandom
После JDK1.7 вместо Random предоставляется новый класс ThreadLocalRandom. Метод использования относительно прост:
В текущем методе есть:
Вы можете видеть, что он будет инициализирован, если он не инициализирован, и здесь наш seed больше не является глобальной переменной, в нашем Thread есть три переменных:- threadLocalRandomSeed: используется для управления начальным числом случайных чисел.
- threadLocalRandomProbe: используется ThreadLocalRandom для управления инициализацией.
- threadLocalRandomSecondarySeed: это вторичное семя.
Вы можете видеть, что все переменные снабжены аннотацией @sun.misc.Contended, которая используется для борьбы с ложным совместным использованием.
Код в методе nextInt() выглядит следующим образом:
Код нашего ключа выглядит следующим образом:
UNSAFE.putLong(t = Thread.currentThread(), SEED,r=UNSAFE.getLong(t, SEED) + GAMMA);
Видно, что поскольку каждый поток поддерживает свое начальное число, CAS в это время не нужен, а put выполняется напрямую.Здесь изоляция между потоками используется для уменьшения конфликтов параллелизма, поэтому ThreadLocalRandom имеет высокую производительность.
4. Данные о производительности
Сравнительный анализ с JMH:
@BenchmarkMode({Mode.AverageTime})
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Warmup(iterations=3, time = 5, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations=3,time = 5)
@Threads(4)
@Fork(1)
@State(Scope.Benchmark)
public class Myclass {
Random random = new Random();
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
@Benchmark
public int measureRandom(){
return random.nextInt();
}
@Benchmark
public int threadLocalmeasureRandom(){
return threadLocalRandom.nextInt();
}
}
параллельные потоки | Random | ThreadLocalRandom |
---|---|---|
1 | 12.798 ns/op | 4.690 ns/op |
4 | 361.027 ns/op | 5.930 ns/op |
16 | 2288.391 ns/op | 22.155 ns/op |
32 | 4812.740 ns/op | 49.144 ns/op |
Видно, что ThreadLocalRandom — это в основном полное злоупотребление Random, и чем выше степень параллелизма, тем больше разрыв.
Наконец
Я считаю, что после прочтения этой статьи, если вы будете использовать случайные числа в практических приложениях в будущем, у вас обязательно появятся новые возможности выбора.
Наконец, эта статья была включена в JGrowing, всеобъемлющий и отличный маршрут изучения Java, совместно созданный сообществом.Если вы хотите участвовать в обслуживании проектов с открытым исходным кодом, вы можете создать его вместе.Адрес github:GitHub.com/Java растет…Пожалуйста, дайте мне маленькую звезду.
Если вы считаете, что в этой статье есть статьи для вас, вы можете подписаться на мой технический паблик.За последнее время автор собрал много свежих обучающих материалов,видео и материалов интервью.Вы можете получить их, обратив внимание.Ваше внимание и пересылка самая большая поддержка для меня. , O(∩_∩)O