Perkenalan
Ada banyak ilmu di dunia yang mempelajari teori probabilitas. Dan ilmu pengetahuan terdiri dari berbagai bagian. Misalnya, dalam matematika ada bagian terpisah yang dikhususkan untuk mempelajari kejadian acak, besaran, dll. Namun sains tidak dianggap enteng. Dalam hal ini, teori probabilitas mulai terbentuk ketika orang mencoba memahami pola apa saja yang ada dalam pelemparan dadu saat memainkan permainan untung-untungan. Jika dicermati, ada banyak hal yang tampaknya acak di sekitar kita. Namun segala sesuatu yang acak tidak sepenuhnya acak. Tapi lebih dari itu nanti. Bahasa pemrograman Java juga memiliki dukungan untuk angka acak, dimulai dengan JDK versi pertama. Angka acak di Java dapat digunakan menggunakan kelas
java.util.Random . Untuk pengujian, kami akan menggunakan
compiler java online tutorialspoint . Berikut adalah contoh primitif penggunaan
Random untuk meniru pelemparan “dadu”, atau kubus dalam bahasa Rusia:
import java.util.Random;
public class HelloWorld{
public static void main(String []args){
Random rnd = new Random();
int number = rnd.nextInt(6) + 1;
System.out.println("Random number: " + number);
}
}
Tampaknya ini bisa menjadi akhir dari deskripsi
Random , tapi tidak sesederhana itu. Mari kita buka deskripsi kelas
java.util.Random di Java API. Dan di sini kita melihat hal-hal menarik. Kelas
Random menggunakan bilangan pseudo-acak. Bagaimana? Ternyata angka acak tidak begitu acak?
Keacakan semu java.util.Random
Dokumentasi untuk kelas
java.util.Random mengatakan bahwa jika instance
Random dibuat dengan parameter
seed yang sama dan urutan tindakan yang sama dilakukan pada instance tersebut, maka instance tersebut akan mengembalikan urutan angka yang identik. Dan jika kita melihat lebih dekat, kita dapat melihat bahwa
Random sebenarnya memiliki
konstruktor yang mengambil nilai
long sebagai
sebuah seed:
Random rnd1 = new Random(1L);
Random rnd2 = new Random(1L);
boolean test = rnd1.nextInt(6) == rnd2.nextInt(6);
System.out.println("Test: " + test);
Contoh ini akan mengembalikan
nilai true karena
benih dari kedua contoh itu sama. Apa yang harus dilakukan? Konstruktor default memecahkan sebagian masalah. Di bawah ini adalah contoh isi konstruktor
Random :
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
Konstruktor default menggunakan operasi
OR eksklusif bitwise . Dan untuk ini menggunakan
long yang mewakili waktu saat ini dan beberapa
seed :
private static long seedUniquifier() {
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
Hal menarik lainnya di sini adalah setiap panggilan ke metode pengambil
seedUniquifier mengubah nilai
seedUniquifier . Artinya, kelas dirancang untuk memilih nomor acak seefisien mungkin. Namun, seperti yang disebutkan dalam dokumentasi, mereka "
tidak aman secara kriptografis ". Artinya, untuk beberapa tujuan penggunaan untuk tujuan kriptografi (pembuatan kata sandi, dll.) tidak cocok, karena urutan dengan pendekatan yang tepat diprediksi. Ada contoh topik ini di Internet, misalnya di sini: “
Memprediksi Math.random() berikutnya di Java ”. Atau misalnya source codenya disini: “
Kerentanan Kripto Lemah ”. java.util.Random (pembuat nomor acak) memiliki “jalan pintas” tertentu,
yaitu versi singkat dari panggilan yang dijalankan melalui Math.random:
public static void main(String []args){
int random_number = 1 + (int) (Math.random() * 6);
System.out.println("Value: " + random_number);
}
Tetapi jika Anda perhatikan lebih dekat, Random yang sama ada di dalamnya:
public static double random() {
return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
static final Random randomNumberGenerator = new Random();
}
JavaDoc menyarankan penggunaan kelas
SecureRandom untuk "
generator nomor pseudo-acak yang aman secara kriptografis ".
Amankan Java Acak
Kelas
SecureRandom adalah subkelas dari
java.util.Random dan terletak di paket
java.security . Perbandingan kedua kelas ini dapat dibaca pada artikel “
Perbedaan Java.util.Random dan Java.security.SecureRandom ”. Mengapa SecureRandom ini begitu bagus? Faktanya adalah baginya sumber bilangan acak adalah hal yang terdengar ajaib seperti “kumpulan entropi inti”. Ini merupakan plus dan minus. Kekurangannya bisa anda baca pada artikel : “
Bahaya java.security.SecureRandom ”. Singkatnya, Linux memiliki generator nomor acak kernel (RNG). RNG menghasilkan angka acak berdasarkan data dari kumpulan entropi, yang diisi berdasarkan kejadian acak dalam sistem, seperti pengaturan waktu keyboard dan disk, pergerakan mouse, interupsi, dan lalu lintas jaringan. Informasi lebih lanjut mengenai entropy pool dijelaskan pada materi "
Angka acak di Linux (RNG) atau cara "mengisi" /dev/random dan /dev/urandom ". Pada sistem Windows, SHA1PRNG digunakan, diimplementasikan di sun.security.provider.SecureRandom. Dengan berkembangnya Java, SecureRandom juga mengalami perubahan, yang patut dibaca di ulasan “
Pembaruan Java SecureRandom per April 2016 ” untuk gambaran lengkapnya.
Multithreading atau menjadi seperti Caesar
Jika Anda melihat kode kelas
Random , sepertinya tidak ada yang menunjukkan masalah. Metode tidak ditandai
disinkronkan . Tapi ada satu TAPI: saat membuat
Random dengan konstruktor default di beberapa thread, kita akan berbagi
seed instance yang sama di antara mereka , yang dengannya
Random akan dibuat . Dan juga ketika nomor acak baru diterima,
AtomicLong internal instance juga berubah . Di satu sisi, tidak ada yang salah dengan hal ini dari sudut pandang logis, karena...
AtomicLong digunakan . Di sisi lain, Anda harus membayar segalanya, termasuk produktivitas. Dan untuk ini juga. Oleh karena itu, bahkan dokumentasi resmi untuk
java.util.Random mengatakan: "
Instance dari java.util.Random adalah threadsafe. Namun, penggunaan instance java.util.Random yang sama secara bersamaan di seluruh thread mungkin akan menemui pertentangan dan akibatnya kinerja yang buruk. Pertimbangkan alih-alih menggunakan ThreadLocalRandom dalam desain multithread ". Artinya, pada aplikasi multi-threaded saat aktif menggunakan
Random dari beberapa thread, lebih baik menggunakan kelas
ThreadLocalRandom . Penggunaannya sedikit berbeda dari
Random biasa :
public static void main(String []args){
int rand = ThreadLocalRandom.current().nextInt(1,7);
System.out.println("Value: " + rand);
}
Seperti yang Anda lihat, kami tidak menentukan
benih untuk itu . Contoh ini dijelaskan dalam tutorial resmi dari Oracle:
Concurrent Random Numbers . Anda dapat membaca lebih lanjut tentang kelas ini di ulasan: "
Panduan ThreadLocalRandom di Java ".
StreamAPI dan Acak
Dengan dirilisnya Java 8, kami memiliki banyak fitur baru. Termasuk API Aliran. Dan perubahan tersebut juga mempengaruhi pembentukan nilai
Random . Misalnya, kelas
Random memiliki metode baru yang memungkinkan Anda mendapatkan
Stream dengan nilai acak seperti
int
,
double
atau
long
. Misalnya:
import java.util.Random;
public class HelloWorld{
public static void main(String []args){
new Random().ints(10, 1, 7).forEach(n -> System.out.println(n));
}
}
Ada juga kelas baru
SplittableRandom :
import java.util.SplittableRandom;
public class HelloWorld{
public static void main(String []args){
new SplittableRandom().ints(10, 1, 7).forEach(n -> System.out.println(n));
}
}
Anda dapat membaca lebih lanjut tentang perbedaan antara
SplittableRandom dan kelas lainnya di sini: "
Berbagai cara membuat angka Acak di Java ".
Kesimpulan
Saya pikir ada baiknya menarik kesimpulan. Anda perlu membaca JavaDoc dengan cermat untuk kelas yang digunakan. Di balik sesuatu yang sekilas sederhana seperti Random, ada nuansa yang bisa memainkan lelucon yang kejam. #Viacheslav
GO TO FULL VERSION