JavaRush /مدونة جافا /Random-AR /نظرية الاحتمالية في الممارسة العملية أو هل تعلم عن Random...
Viacheslav
مستوى

نظرية الاحتمالية في الممارسة العملية أو هل تعلم عن Random

نشرت في المجموعة
نظرية الاحتمالية في الممارسة العملية أو هل تعلم عن العشوائية - 1

مقدمة

هناك العديد من العلوم في العالم التي تدرس نظرية الاحتمالات. والعلوم تتكون من أقسام مختلفة. على سبيل المثال، يوجد في الرياضيات قسم منفصل مخصص لدراسة الأحداث العشوائية والكميات وما إلى ذلك. لكن العلم لا يؤخذ على محمل الجد. في هذه الحالة، بدأت نظرية الاحتمالية في التبلور عندما حاول الناس فهم الأنماط الموجودة في رمي النرد عند ممارسة ألعاب الحظ. إذا نظرت عن كثب، هناك العديد من الأشياء التي تبدو عشوائية من حولنا. لكن كل شيء عشوائي ليس عشوائيًا تمامًا. ولكن أكثر عن ذلك لاحقا. تتمتع لغة برمجة 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 أرقامًا شبه عشوائية. كيف ذلك؟ اتضح أن الأرقام العشوائية ليست عشوائية جدا؟
نظرية الاحتمالية في الممارسة العملية أو هل تعلم عن العشوائية - 2

العشوائية الزائفة java.util.Random

تشير وثائق فئة java.util.Random إلى أنه إذا تم إنشاء مثيلات Random باستخدام نفس المعلمة الأولية وتم تنفيذ نفس تسلسل الإجراءات على المثيلات، فإنها تُرجع تسلسلات متطابقة من الأرقام. وإذا نظرنا عن كثب، يمكننا أن نرى أن Random لديه في الواقع مُنشئ يأخذ بعض القيمة الطويلة كبذرة:
Random rnd1 = new Random(1L);
Random rnd2 = new Random(1L);
boolean test = rnd1.nextInt(6) == rnd2.nextInt(6);
System.out.println("Test: " + test);
سيعود هذا المثال صحيحًا لأن بذرة كلتا الحالتين هي نفسها. ما يجب القيام به؟ المنشئ الافتراضي يحل المشكلة جزئيًا. فيما يلي مثال لمحتويات المنشئ العشوائي :
public Random() {
	this(seedUniquifier() ^ System.nanoTime());
}
يستخدم المُنشئ الافتراضي عملية OR الحصرية للبت . ويستخدم long لتمثيل الوقت الحالي وبعض البذور لهذا :
private static long seedUniquifier() {
	for (;;) {
		long current = seedUniquifier.get();
		long next = current * 181783497276652981L;
		if (seedUniquifier.compareAndSet(current, next))
			return next;
	}
}
شيء آخر مثير للاهتمام هنا هو أن كل استدعاء لأسلوب getUniquifier الخاص بـ 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 من أجل " مولد أرقام عشوائية زائفة آمن تشفيريًا ".
نظرية الاحتمالية في الممارسة العملية أو هل تعلم عن العشوائية - 3

تأمين جافا عشوائية

فئة SecureRandom هي فئة فرعية من java.util.Random وتقع في حزمة java.security . يمكن قراءة المقارنة بين هاتين الفئتين في المقالة " الفرق بين java.util.Random وjava.security.SecureRandom ". لماذا يعتبر SecureRandom جيدًا جدًا؟ والحقيقة هي أن مصدر الأرقام العشوائية بالنسبة له هو شيء يبدو سحريًا مثل "مجمع الإنتروبيا الأساسي". هذا هو زائد وناقص. يمكنك أن تقرأ عن عيوب ذلك في المقال: " مخاطر java.security.SecureRandom ". باختصار، لدى Linux مولد أرقام عشوائية (RNG). ينشئ RNG أرقامًا عشوائية بناءً على بيانات من مجمع الإنتروبيا، والتي يتم ملؤها بناءً على الأحداث العشوائية في النظام، مثل توقيت لوحة المفاتيح والقرص، وحركات الماوس، والمقاطعات، وحركة مرور الشبكة. مزيد من المعلومات حول مجمع الإنتروبيا موصوفة في المادة " الأرقام العشوائية في Linux (RNG) أو كيفية "ملء" /dev/random و /dev/urandom ". في أنظمة Windows، يتم استخدام SHA1PRNG، ويتم تنفيذه في sun.security.provider.SecureRandom. مع تطور Java، تغير SecureRandom أيضًا، وهو ما يستحق القراءة عنه في مراجعة " تحديثات Java SecureRandom اعتبارًا من أبريل 2016 " للحصول على صورة كاملة.
نظرية الاحتمالية في الممارسة العملية أو هل تعلم عن العشوائية - 4

تعدد الخيوط أو يكون مثل قيصر

إذا نظرت إلى كود الفئة Random ، فلا يبدو أن هناك شيئًا يشير إلى وجود مشكلة. لم يتم وضع علامة على الأساليب متزامنة . ولكن هناك شيء واحد: عند إنشاء Random باستخدام المنشئ الافتراضي في عدة سلاسل رسائل، سنشارك نفس مثيل المثيل بينهما ، والذي سيتم من خلاله إنشاء Random . وأيضًا عند تلقي رقم عشوائي جديد، يتغير AtomicLong الداخلي للمثيل أيضًا . فمن ناحية، لا حرج في ذلك من الناحية المنطقية، لأن... يتم استخدام AtomicLong . ومن ناحية أخرى، عليك أن تدفع مقابل كل شيء، بما في ذلك الإنتاجية. ولهذا أيضا. لذلك، حتى الوثائق الرسمية لـ java.util.Random تقول: " مثيلات java.util.Random آمنة لمؤشر الترابط. ومع ذلك، فإن الاستخدام المتزامن لنفس مثيل 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: الأرقام العشوائية المتزامنة . يمكنك قراءة المزيد عن هذا الفصل في المراجعة: " دليل ThreadLocalRandom في Java ".
نظرية الاحتمالية في الممارسة العملية أو هل تعلم عن العشوائية - 5

StreamAPI وعشوائية

مع إصدار Java 8، لدينا العديد من الميزات الجديدة. بما في ذلك Stream API. وأثرت التغييرات أيضًا على توليد القيم العشوائية . على سبيل المثال، تحتوي الفئة Random على طرق جديدة تتيح لك الحصول على دفق بقيم عشوائية مثل intأو . على سبيل المثال: doublelong
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، هناك فروق دقيقة يمكن أن تلعب مزحة قاسية. # فياتشيسلاف
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION