Giới thiệu
Trên thế giới có rất nhiều ngành khoa học nghiên cứu lý thuyết xác suất. Và khoa học bao gồm nhiều phần khác nhau. Ví dụ, trong toán học có một phần riêng dành cho việc nghiên cứu các sự kiện, số lượng ngẫu nhiên, v.v. Nhưng khoa học không được xem nhẹ. Trong trường hợp này, lý thuyết về xác suất bắt đầu hình thành khi mọi người cố gắng hiểu những mô hình ném xúc xắc khi chơi trò chơi may rủi. Nếu để ý kỹ, xung quanh chúng ta có rất nhiều thứ tưởng chừng như ngẫu nhiên. Nhưng mọi thứ ngẫu nhiên không hoàn toàn ngẫu nhiên. Nhưng nhiều hơn về điều này sau. Ngôn ngữ lập trình Java cũng hỗ trợ các số ngẫu nhiên, bắt đầu từ phiên bản đầu tiên của JDK. Các số ngẫu nhiên trong Java có thể được sử dụng bằng lớp
java.util.Random . Để thử nghiệm, chúng tôi sẽ sử dụng
trình biên dịch trực tuyến java tutorialspoint . Đây là một ví dụ cơ bản về việc sử dụng
Ngẫu nhiên để mô phỏng việc ném “xúc xắc” hoặc hình khối bằng tiếng Nga:
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);
}
}
Có vẻ như đây có thể là phần kết thúc mô tả của
Random , nhưng nó không đơn giản như vậy. Hãy mở mô tả của lớp
java.util.Random trong API Java. Và ở đây chúng ta thấy những điều thú vị. Lớp
Random sử dụng số giả ngẫu nhiên. Làm sao vậy? Hóa ra số ngẫu nhiên không phải ngẫu nhiên như vậy?
Tính giả ngẫu nhiên java.util.Random
Tài liệu dành cho lớp
java.util.Random nói rằng nếu các phiên bản
Random được tạo với cùng tham số
hạt giống và các chuỗi hành động giống nhau được thực hiện trên các phiên bản đó thì chúng sẽ trả về các chuỗi số giống hệt nhau. Và nếu nhìn kỹ hơn, chúng ta có thể thấy rằng
Random thực sự có
một hàm tạo lấy một số giá trị
dài làm
hạt giống:
Random rnd1 = new Random(1L);
Random rnd2 = new Random(1L);
boolean test = rnd1.nextInt(6) == rnd2.nextInt(6);
System.out.println("Test: " + test);
Ví dụ này sẽ trả về
đúng vì
hạt giống của cả hai trường hợp đều giống nhau. Phải làm gì? Hàm tạo mặc định giải quyết được một phần vấn đề. Dưới đây là một ví dụ về nội dung của hàm tạo
ngẫu nhiên :
public Random() {
this(seedUniquifier() ^ System.nanoTime());
}
Hàm tạo mặc định sử dụng phép toán
OR độc quyền theo bit .
Và sử dụng một long đại diện cho thời gian hiện tại và một số
hạt giống cho việc này :
private static long seedUniquifier() {
for (;;) {
long current = seedUniquifier.get();
long next = current * 181783497276652981L;
if (seedUniquifier.compareAndSet(current, next))
return next;
}
}
Một điều thú vị khác ở đây là mỗi lệnh gọi phương thức getter
seederUniquifier sẽ thay đổi giá trị
của SeedUniquifier . Nghĩa là, lớp được thiết kế để chọn số ngẫu nhiên một cách hiệu quả nhất có thể. Tuy nhiên, như tài liệu nói, chúng "
không an toàn về mặt mật mã ". Nghĩa là, đối với một số mục đích sử dụng cho mục đích mã hóa (tạo mật khẩu, v.v.), nó không phù hợp, bởi vì trình tự với cách tiếp cận thích hợp được dự đoán. Có những ví dụ về chủ đề này trên Internet, ví dụ ở đây: “
Dự đoán Math.random() tiếp theo trong Java ”. Hoặc ví dụ mã nguồn ở đây: "
Vulnerability Weak Crypto ". java.util.Random (trình tạo số ngẫu nhiên) có một “phím tắt” nhất định, đó
là phiên bản rút gọn của lệnh gọi được thực hiện thông qua Math.random:
public static void main(String []args){
int random_number = 1 + (int) (Math.random() * 6);
System.out.println("Value: " + random_number);
}
Nhưng nếu bạn nhìn kỹ, Random sẽ nằm bên trong:
public static double random() {
return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
}
private static final class RandomNumberGeneratorHolder {
static final Random randomNumberGenerator = new Random();
}
JavaDoc khuyên bạn nên sử dụng lớp
SecureRandom cho "
trình tạo số giả ngẫu nhiên được bảo mật bằng mật mã ".
Java ngẫu nhiên an toàn
Lớp
SecureRandom là một lớp con của
java.util.Random và nằm trong gói
java.security . Bạn có thể đọc so sánh giữa hai lớp này trong bài viết "
Sự khác biệt giữa java.util.Random và java.security.SecureRandom ". Tại sao SecureRandom này lại tốt như vậy? Thực tế là đối với anh ta, nguồn của các số ngẫu nhiên là một thứ nghe có vẻ kỳ diệu như “nhóm entropy cốt lõi”. Đây vừa là điểm cộng vừa là điểm trừ. Bạn có thể đọc về nhược điểm của việc này trong bài viết: “
Sự nguy hiểm của java.security.SecureRandom ”. Nói tóm lại, Linux có bộ tạo số ngẫu nhiên kernel (RNG). RNG tạo các số ngẫu nhiên dựa trên dữ liệu từ nhóm entropy, được điền dựa trên các sự kiện ngẫu nhiên trong hệ thống, chẳng hạn như thời gian của bàn phím và ổ đĩa, chuyển động của chuột, ngắt và lưu lượng mạng. Thông tin thêm về nhóm entropy được mô tả trong tài liệu "
Số ngẫu nhiên trong Linux (RNG) hoặc cách "điền" /dev/random và /dev/urandom ". Trên hệ thống Windows, SHA1PRNG được sử dụng và triển khai trong sun.security.provider.SecureRandom. Với sự phát triển của Java, SecureRandom cũng đã thay đổi, điều này đáng đọc trong bài đánh giá “
Cập nhật Java SecureRandom kể từ tháng 4 năm 2016 ” để có bức tranh hoàn chỉnh.
Đa luồng hoặc giống như Caesar
Nếu bạn nhìn vào mã của lớp
Random , dường như không có gì cho thấy có sự cố. Các phương thức không được đánh dấu
là đồng bộ hóa . Nhưng có một điều NHƯNG: khi tạo
Random với hàm tạo mặc định trong một số luồng, chúng ta sẽ chia sẻ cùng một
instance giữa chúng , qua đó
Random sẽ được tạo . Và khi nhận được một số ngẫu nhiên mới,
AtomicLong bên trong của cá thể cũng thay đổi . Một mặt, điều này không có gì sai về mặt logic, bởi vì...
AtomicLong được sử dụng . Mặt khác, bạn phải trả tiền cho mọi thứ, kể cả năng suất. Và vì điều này nữa. Do đó, ngay cả tài liệu chính thức về
java.util.Random cũng nói: "
Các phiên bản của java.util.Random là các luồng an toàn. Tuy nhiên, việc sử dụng đồng thời cùng một phiên bản java.util.Random trên các luồng có thể gặp phải sự tranh chấp và hậu quả là hiệu suất kém. Hãy xem xét thay vào đó sử dụng ThreadLocalRandom trong các thiết kế đa luồng ". Nghĩa là, trong các ứng dụng đa luồng khi tích cực sử dụng
Random từ nhiều luồng, tốt hơn nên sử dụng lớp
ThreadLocalRandom . Cách sử dụng của nó hơi khác so với
Random thông thường :
public static void main(String []args){
int rand = ThreadLocalRandom.current().nextInt(1,7);
System.out.println("Value: " + rand);
}
Như bạn có thể thấy, chúng tôi không chỉ định
hạt giống cho nó . Ví dụ này được mô tả trong hướng dẫn chính thức của Oracle:
Concurrent Random Numbers . Bạn có thể đọc thêm về lớp này trong bài đánh giá: "
Guide to ThreadLocalRandom in Java ".
StreamAPI và ngẫu nhiên
Với việc phát hành Java 8, chúng tôi có nhiều tính năng mới. Bao gồm API luồng. Và những thay đổi cũng ảnh hưởng đến việc tạo ra các giá trị
Ngẫu nhiên . Ví dụ: lớp
Random có các phương thức mới cho phép bạn lấy
Stream với các giá trị ngẫu nhiên như
int
,
double
hoặc
long
. Ví dụ:
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));
}
}
Ngoài ra còn có một lớp mới
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));
}
}
Bạn có thể đọc thêm về sự khác biệt giữa
SplittableRandom và các lớp khác tại đây: "
Các cách khác nhau để tạo số ngẫu nhiên trong Java ".
Phần kết luận
Tôi nghĩ rằng nó đáng để đưa ra kết luận. Bạn cần đọc kỹ JavaDoc về các lớp được sử dụng. Đằng sau một thứ thoạt nhìn đơn giản như Random, có những sắc thái có thể tạo ra một trò đùa độc ác. #Viacheslav
GO TO FULL VERSION