מדריך ל- ThreadLocalRandom בג'אווה

1. סקירה כללית

יצירת ערכים אקראיים היא משימה נפוצה מאוד. זו הסיבה שג'אווה מספקת את java.util.Random מעמד.

עם זאת, מחלקה זו אינה מתפקדת היטב בסביבה מרובת נושאים.

באופן פשוט, הסיבה לביצועים הירודים של אַקרַאִי בסביבה מרובת-שרשורים נובעת ממחלוקת - בהתחשב בכך שרבים משורשרים חולקים אותו דבר אַקרַאִי למשל.

כדי לטפל במגבלה זו, ג'אווה הציגה את java.util.concurrent.ThreadLocalRandom בכיתה ב- JDK 7 - ליצירת מספרים אקראיים בסביבה מרובת שרשראות.

בואו נראה איך ThreadLocalRandom מבצע וכיצד להשתמש בו ביישומים בעולם האמיתי.

2. ThreadLocalRandom על אַקרַאִי

ThreadLocalRandom הוא שילוב של ThreadLocal ו אַקרַאִי שיעורים (על כך בהמשך) ומבודד לשרשור הנוכחי. לפיכך, היא משיגה ביצועים טובים יותר בסביבה מרובת הברגה על ידי הימנעות מכל גישה בו זמנית למופעים של אַקרַאִי.

המספר האקראי שמתקבל בשרשור אחד אינו מושפע מהשרשור השני, ואילו java.util.Random מספק מספרים אקראיים ברחבי העולם.

כמו כן, בניגוד אַקרַאִי,ThreadLocalRandom אינו תומך בהגדרת הזרע במפורש. במקום זאת, זה עוקף את setSeed (זרע ארוך) שיטה שעברה בירושה אַקרַאִי לזרוק תמיד לא נתמךOperationException אם נקרא.

2.1. מחלוקת חוטים

עד כה קבענו כי אַקרַאִי הכיתה מתפקדת בצורה גרועה בסביבות בו זמנית. כדי להבין זאת טוב יותר, בואו נראה כיצד אחת הפעולות העיקריות שלה, הבא (int), מיושם:

גמר פרטי AtomicLong seed; מוגן int next (int bits) {long oldseed, nextseed; זרע AtomicLong = זה זרע; לעשות {oldseed = seed.get (); nextseed = (oldseed * מכפיל + תוספת) & מסכה; } בעוד (! seed.compareAndSet (oldseed, nextseed)); החזר (int) (nextseed >>> (48 - ביט)); }

זהו יישום Java עבור האלגוריתם Generator Congruential Generator. ברור כי כל הנושאים חולקים אותו דבר זֶרַע משתנה למשל.

כדי ליצור את ערכת הביטים האקראית הבאה, היא מנסה תחילה לשנות את המשותף זֶרַע ערך אטומי באמצעות CompareAndSet אוֹ CAS בקיצור.

כאשר מספר שרשורים מנסים לעדכן את זֶרַע במקביל לשימוש ב- CAS, חוט אחד זוכה ומעדכן את זֶרַע, והשאר מפסידים. אובדן שרשורים ינסה את אותו התהליך שוב ושוב עד שיקבלו הזדמנות לעדכן את הערך ו- בסופו של דבר ליצור את המספר האקראי.

אלגוריתם זה אינו נעול וחוטים שונים יכולים להתקדם במקביל. למרות זאת, כאשר הטענה גבוהה, מספר כישלונות CAS וניסויים חוזרים יפגעו בביצועים הכוללים.

מצד שני, ה ThreadLocalRandom מסיר לחלוטין את הטענה הזו, מכיוון שלכל חוט יש מופע משלה אַקרַאִי וכתוצאה מכך היא מוגבלת משלה זֶרַע.

בואו נסתכל על כמה דרכים להפיק אקראיות int, ארוך ו לְהַכפִּיל ערכים.

3. יצירת ערכים אקראיים באמצעות ThreadLocalRandom

בהתאם לתיעוד האורקל, אנחנו רק צריכים להתקשר ThreadLocalRandom.current () שיטה, והיא תחזיר את המופע של ThreadLocalRandom לשרשור הנוכחי. לאחר מכן נוכל ליצור ערכים אקראיים על ידי הפעלת שיטות מופע זמינות של המחלקה.

בואו ניצור אקראי int ערך ללא גבולות:

int unboundedRandomValue = ThreadLocalRandom.current (). nextInt ());

לאחר מכן, בואו נראה כיצד אנו יכולים ליצור גבולות אקראיים int ערך, כלומר ערך שבין הגבול התחתון והעליון הנתון.

הנה דוגמה ליצירת אקראי int ערך בין 0 ל 100:

int boundedRandomValue = ThreadLocalRandom.current (). nextInt (0, 100);

שימו לב, 0 היא הגבול התחתון הכולל ו 100 היא הגבול העליון הבלעדי.

אנו יכולים ליצור ערכים אקראיים עבור ארוך ו לְהַכפִּיל על ידי הפעלת nextLong () ו nextDouble () שיטות באופן דומה כפי שמוצג בדוגמאות לעיל.

Java 8 מוסיף גם את הבאגאוסי () שיטה לייצר את הערך הבא להפצה רגילה עם ממוצע 0.0 וסטיית תקן 1.0 מרצף הגנרטור.

כמו עם אַקרַאִי בכיתה, אנחנו יכולים גם להשתמש ב- זוגות (), ints () ו מייחל () שיטות להפקת זרמים של ערכים אקראיים.

4. השוואה ThreadLocalRandom ו אַקרַאִי באמצעות JMH

בואו נראה כיצד נוכל ליצור ערכים אקראיים בסביבה מרובת הליכי משנה, על ידי שימוש בשתי המחלקות, ואז להשוות את הביצועים שלהם באמצעות JMH.

ראשית, בואו ניצור דוגמה שבה כל האשכולות חולקות מופע יחיד של אַקרַאִי. כאן אנו מגישים את המשימה ליצור ערך אקראי באמצעות ה- אַקרַאִי למשל ל- שירות ExecutorS:

ExecutorService executor = Executors.newWorkStealingPool (); רשימה callables = ArrayList חדש (); אקראי אקראי = אקראי חדש (); עבור (int i = 0; i {return random.nextInt ();}); } executor.invokeAll (callables);

בואו לבדוק את ביצועי הקוד שלמעלה באמצעות JMH ביצועים:

# הפעל הושלם. זמן כולל: 00:00:36 מצב מידוד Cnt ציון שגיאות יחידות אשכול LocalRandomBenchMarker.randomValuesUsingRandom avgt 20 771.613 ± 222.220 us / op

באופן דומה, בואו נשתמש כעת ThreadLocalRandom במקום ה אַקרַאִי למשל, המשתמשת במופע אחד של ThreadLocalRandom לכל חוט בבריכה:

ExecutorService executor = Executors.newWorkStealingPool (); רשימה callables = ArrayList חדש (); עבור (int i = 0; i {return ThreadLocalRandom.current (). nextInt ();}); } executor.invokeAll (callables);

הנה תוצאת השימוש ThreadLocalRandom:

# הפעל הושלם. זמן כולל: 00:00:36 מצב מידוד Cnt ציון יחידות שגיאה חוט LocalRandomBenchMarker.randomWaluesUsingThreadLocalRandom avgt 20 624.911 ± 113.268 us / op

לבסוף, על ידי השוואת תוצאות JMH לעיל עבור שניהם אַקרַאִי ו ThreadLocalRandom, אנו יכולים לראות בבירור כי הזמן הממוצע שנדרש להפקת 1000 ערכים אקראיים באמצעות אַקרַאִי הוא 772 מיקרו שניות, ואילו השימוש ב ThreadLocalRandom זה בערך 625 מיקרו שניות.

לפיכך, אנו יכולים להסיק זאת ThreadLocalRandom יעיל יותר בסביבה בו זמנית.

ללמוד עוד על JMH, עיין במאמר הקודם שלנו כאן.

5. פרטי יישום

זה מודל נפשי טוב לחשוב על ThreadLocalRandom כשילוב של ThreadLocal ו אַקרַאִי שיעורים. לאמיתו של דבר, המודל הנפשי הזה התאים ליישום בפועל לפני Java 8.

אולם נכון ל- Java 8, יישור זה נשבר לחלוטין כמו ה- ThreadLocalRandom הפך לסינגלטון. הנה איך נוֹכְחִי() השיטה נראית ב- Java 8+:

מופע סופי סטטי ThreadLocalRandom = ThreadLocalRandom חדש (); הנוכחי סטטי ציבורי ThreadLocalRandom () {if (U.getInt (Thread.currentThread (), PROBE) == 0) localInit (); מופע חזרה; }

נכון לשתף אחד גלובלי אַקרַאִי מופע מוביל לביצועים לא מיטביים במחלוקת גבוהה. עם זאת, שימוש במופע ייעודי אחד לכל שרשור הוא גם מוגזם.

במקום מופע ייעודי של אַקרַאִי לכל שרשור, כל שרשור צריך רק לשמור על שלו זֶרַע ערך. נכון ל- Java 8, ה- פְּתִיל הכיתה עצמה הותאמה מחדש כדי לשמור על זֶרַע ערך:

מחלקה ציבורית מיישמת חוטים ניתן להפעיל {// הושמט @ jdk.internal.vm.annotation.Contended ("tlr") חוט ארוך LocalRandomSeed; @ jdk.internal.vm.annotation.Contended ("tlr") int threadLocalRandomProbe; @ jdk.internal.vm.annotation.Contended ("tlr") int threadLocalRandomSecondarySeed; }

ה threadLocalRandomSeed משתנה אחראי על שמירת ערך הזרע הנוכחי עבור ThreadLocalRandom. יתר על כן, הזרע המשני, threadLocalRandomSecondarySeed, משמש בדרך כלל באופן פנימי על ידי אנשים כמו ForkJoinPool.

יישום זה כולל כמה אופטימיזציות לביצוע ThreadLocalRandom ביצועים עוד יותר:

  • הימנעות משיתוף כוזב באמצעות @מְרוּצֶה ביאור, שבעצם מוסיף ריפוד מספיק בכדי לבודד את המשתנים המתמודדים בקווי המטמון שלהם
  • באמצעות sun.misc. לא בטוח לעדכן את שלושת המשתנים הללו במקום להשתמש ב- Reflection API
  • הימנעות מחיפושים חשיפים נוספים הקשורים ל- ThreadLocal יישום

6. מסקנה

מאמר זה המחיש את ההבדל בין java.util.Random ו java.util.concurrent.ThreadLocalRandom.

ראינו גם את היתרון של ThreadLocalRandom על אַקרַאִי בסביבה מרובת הליכי משנה, כמו גם ביצועים ואיך נוכל ליצור ערכים אקראיים באמצעות הכיתה.

ThreadLocalRandom היא תוספת פשוטה ל- JDK, אך היא יכולה ליצור השפעה ניכרת ביישומים בו זמנית.

וכמו תמיד, ניתן למצוא את היישום של כל הדוגמאות הללו ב- GitHub.


$config[zx-auto] not found$config[zx-overlay] not found