การแนะนำ
มีวิทยาศาสตร์มากมายในโลกที่ศึกษาทฤษฎีความน่าจะเป็น และวิทยาศาสตร์ประกอบด้วยส่วนต่างๆ ตัวอย่างเช่น ในทางคณิตศาสตร์มีส่วนแยกต่างหากสำหรับการศึกษาเหตุการณ์สุ่ม ปริมาณ ฯลฯ แต่วิทยาศาสตร์ไม่ได้ถูกมองว่าเบา ในกรณีนี้ ทฤษฎีความน่าจะเป็นเริ่มเป็นรูปเป็นร่างเมื่อผู้คนพยายามทำความเข้าใจว่ารูปแบบการขว้างลูกเต๋าเมื่อเล่นเกมเสี่ยงโชคมีรูปแบบใดบ้าง หากมองใกล้ ๆ มีหลายสิ่งที่ดูเหมือนสุ่มอยู่รอบตัวเรา แต่การสุ่มทุกอย่างไม่ใช่การสุ่มโดยสมบูรณ์ แต่จะเพิ่มเติมในภายหลัง ภาษาการเขียนโปรแกรม Java ยังรองรับตัวเลขสุ่มโดยเริ่มจาก JDK เวอร์ชันแรก สามารถใช้ตัวเลขสุ่มใน Java ได้โดยใช้คลาส
java.util.Random สำหรับการทดสอบ เราจะใช้
Tutorialspoint Java Online Compiler นี่คือตัวอย่างดั้งเดิมของการใช้
Randomเพื่อจำลองการขว้าง "ลูกเต๋า" หรือลูกบาศก์ในภาษารัสเซีย:
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);
}
}
ดูเหมือนว่านี่อาจเป็นจุดสิ้นสุดของคำอธิบายของ
Randomแต่มันไม่ง่ายขนาดนั้น เรามาเปิดคำอธิบายของ คลาส
java.util.Randomใน Java API กัน และที่นี่เราเห็นสิ่งที่น่าสนใจ คลาส
Randomใช้ตัวเลขสุ่มหลอก ยังไงล่ะ? ปรากฎว่าตัวเลขสุ่มไม่สุ่มขนาดนั้นเหรอ?
การสุ่มหลอก java.util.Random
เอกสารประกอบสำหรับ คลาส
java.util.Randomระบุว่าหากอินสแตนซ์
ของ Randomถูกสร้างขึ้นด้วย พารามิเตอร์
seed เดียวกัน และมีการดำเนินการตามลำดับเดียวกันบนอินสแตนซ์ อินสแตนซ์ก็จะส่งคืนลำดับตัวเลขที่เหมือนกัน และถ้าเรามองใกล้ ๆ เราจะเห็นว่า
Randomมี
Constructorที่ใช้ ค่า
ยาวเป็นค่า
Seed:
Random rnd1 = new Random(1L);
Random rnd2 = new Random(1L);
boolean test = rnd1.nextInt(6) == rnd2.nextInt(6);
System.out.println("Test: " + test);
ตัวอย่างนี้จะคืน
ค่าเป็นจริงเพราะ
เมล็ดของทั้งสองกรณีจะเหมือนกัน จะทำอย่างไร? ตัวสร้างเริ่มต้นช่วยแก้ปัญหาได้บางส่วน ด้านล่างนี้เป็นตัวอย่างของเนื้อหาของ
Random Constructor :
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
ตัวสร้างเริ่มต้นใช้การดำเนินการแบบเอกสิทธิ์ระดับบิต
หรือ และใช้ longแทนเวลาปัจจุบันและ
เมล็ด บางส่วน สำหรับสิ่งนี้:
private static long seedUniquifier() {
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
สิ่ง ที่น่าสนใจอีกอย่างหนึ่งก็คือ การเรียกใช้ เมธอด getter
ของ seedUniquifier แต่ละครั้ง จะเปลี่ยนค่า
ของ seedUniquifier นั่นคือคลาสได้รับการออกแบบให้เลือกตัวเลขสุ่มอย่างมีประสิทธิภาพมากที่สุด อย่างไรก็ตาม ตามที่เอกสารระบุไว้ พวกเขา "
ไม่ปลอดภัยด้วยการเข้ารหัส " นั่นคือเพื่อวัตถุประสงค์บางประการในการใช้งานเพื่อวัตถุประสงค์ในการเข้ารหัส (การสร้างรหัสผ่าน ฯลฯ ) มันไม่เหมาะเพราะ ทำนายลำดับด้วยแนวทางที่เหมาะสม มีตัวอย่างในหัวข้อนี้บนอินเทอร์เน็ต เช่น “
การทำนาย Math.random() ถัดไปใน Java ” หรือตัวอย่างซอร์สโค้ดที่นี่: "
Vulnerability Weak Crypto " java.util.Random (ตัวสร้างตัวเลขสุ่ม) มี "ทางลัด" บางอย่าง นั่น
คือเวอร์ชันย่อของการเรียกที่ดำเนินการผ่าน Math.random:
public static void main(String []args){
int random_number = 1 + (int) (Math.random() * 6);
System.out.println("Value: " + random_number);
}
แต่ถ้าคุณดูให้ดี Random เดียวกันก็อยู่ข้างใน:
public static double random() {
return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
static final Random randomNumberGenerator = new Random();
}
JavaDoc แนะนำให้ใช้ คลาส
SecureRandomสำหรับ "
ตัวสร้างตัวเลขสุ่มหลอกที่ปลอดภัยแบบเข้ารหัส "
รักษาความปลอดภัย Java แบบสุ่ม
คลาส
SecureRandomเป็นคลาสย่อยของ
java.util.Randomและอยู่ในแพ็คเกจ
java.security คุณสามารถอ่านการเปรียบเทียบของทั้งสองคลาสนี้ได้ในบทความ "
ความแตกต่างระหว่าง java.util.Random และ java.security.SecureRandom " ทำไม SecureRandom ถึงดีขนาดนี้? ความจริงก็คือสำหรับเขาแล้ว แหล่งที่มาของตัวเลขสุ่มนั้นฟังดูมหัศจรรย์ราวกับ "แหล่งรวมเอนโทรปีหลัก" นี่เป็นทั้งบวกและลบ คุณสามารถอ่านเกี่ยวกับข้อเสียของสิ่งนี้ได้ในบทความ: “
อันตรายของ java.security.SecureRandom ” กล่าวโดยสรุป Linux มีตัวสร้างตัวเลขสุ่มเคอร์เนล (RNG) RNG สร้างตัวเลขสุ่มตามข้อมูลจากกลุ่มเอนโทรปี ซึ่งเติมตามเหตุการณ์สุ่มในระบบ เช่น การกำหนดเวลาของแป้นพิมพ์และดิสก์ การเคลื่อนไหวของเมาส์ การขัดจังหวะ และการรับส่งข้อมูลเครือข่าย ข้อมูลเพิ่มเติมเกี่ยวกับกลุ่มเอนโทรปีอธิบายไว้ในเนื้อหา "
Random Numbers in Linux (RNG) หรือวิธี "เติม" /dev/random และ /dev/urandom " บนระบบ Windows จะใช้ SHA1PRNG และนำไปใช้ใน sun.security.provider.SecureRandom ด้วยการพัฒนาของ Java SecureRandom ก็เปลี่ยนไปเช่นกันซึ่งควรอ่านในการทบทวน“
การอัปเดต Java SecureRandom ณ เดือนเมษายน 2559 ” เพื่อให้ได้ภาพรวมที่สมบูรณ์
มัลติเธรดหรือเป็นเหมือนซีซาร์
หากคุณดูโค้ดของ คลาส
Randomดูเหมือนจะไม่มีอะไรบ่งบอกถึงปัญหา วิธีการไม่ถูกทำเครื่องหมายว่า
ซิงโครไนซ์ แต่มีอย่างหนึ่งคือ: เมื่อสร้าง
Randomด้วย Constructor เริ่มต้นในหลายเธรด เราจะแชร์
seed เดียวกันระหว่างเธรดเหล่านั้น โดยที่Random
จะ ถูกสร้างขึ้น และเมื่อได้รับหมายเลขสุ่มใหม่
AtomicLong ภายใน ของอินสแตนซ์ก็จะเปลี่ยนไปด้วย ในแง่หนึ่ง ไม่มีอะไรผิดปกติกับเรื่องนี้จากมุมมองเชิงตรรกะ เพราะ... ใช้
AtomicLong _ ในทางกลับกัน คุณต้องจ่ายทุกอย่าง รวมถึงประสิทธิภาพการทำงานด้วย และสำหรับเรื่องนี้ด้วย ดังนั้น แม้แต่เอกสารอย่างเป็นทางการสำหรับ
java.util.Randomก็บอกว่า: "
อินสแตนซ์ของ java.util.Random เป็นแบบ threadsafe อย่างไรก็ตาม การใช้อินสแตนซ์ java.util.Random เดียวกันพร้อมกันข้ามเธรดอาจพบข้อขัดแย้งและเป็นผลให้ประสิทธิภาพไม่ดี พิจารณา แทนที่จะใช้ ThreadLocalRandom ในการออกแบบแบบมัลติเธรด ". นั่นคือในแอปพลิเคชันแบบมัลติเธรดเมื่อใช้
Randomจากหลายเธรดควรใช้ คลาส
ThreadLocalRandomจะ ดีกว่า การใช้งานแตกต่างจากปกติเล็กน้อย
Random :
public static void main(String []args){
int rand = ThreadLocalRandom.current().nextInt(1,7);
System.out.println("Value: " + rand);
}
อย่างที่คุณเห็น เราไม่ได้ระบุ
เมล็ดพันธุ์ไว้ ตัวอย่าง นี้ได้อธิบายไว้ในบทช่วยสอนอย่างเป็นทางการจาก Oracle:
Concurrent Random Numbers คุณสามารถอ่านเพิ่มเติมเกี่ยวกับคลาสนี้ได้ในการทบทวน: "
Guide to ThreadLocalRandom in Java "
StreamAPI และสุ่ม
ด้วยการเปิดตัว Java 8 เรามีคุณสมบัติใหม่มากมาย รวมถึงสตรีม API และการเปลี่ยนแปลงยังส่งผลต่อการสร้างค่า
สุ่ม ด้วย ตัวอย่างเช่น คลาส
Random มีวิธีการ ใหม่ที่ให้คุณรับ
Streamที่มีค่าสุ่มเช่น
int
หรือ ตัวอย่างเช่น:
double
long
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));
}
}
นอกจากนี้ยังมีคลาสใหม่
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));
}
}
คุณสามารถอ่าน เพิ่มเติมเกี่ยวกับความแตกต่างระหว่าง
SplittableRandomและคลาสอื่นๆ ได้ที่นี่: "
วิธีต่างๆ ในการสร้างตัวเลขสุ่มใน Java "
บทสรุป
ฉันคิดว่ามันคุ้มค่าที่จะได้ข้อสรุป คุณต้องอ่าน JavaDoc อย่างละเอียดสำหรับคลาสที่ใช้ เบื้องหลังบางสิ่งที่เรียบง่ายตั้งแต่แรกเห็นอย่าง Random มีความแตกต่างที่สามารถเล่นเป็นเรื่องตลกที่โหดร้ายได้ #เวียเชสลาฟ
GO TO FULL VERSION