מדריך sun.misc. לא בטוח

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

במאמר זה נסתכל על שיעור מרתק שמספק ה- JRE - מְסוּכָּן מ ה שמש. שונות חֲבִילָה. מחלקה זו מספקת לנו מנגנונים ברמה נמוכה שתוכננו לשימוש רק על ידי ספריית הליבה של Java ולא על ידי משתמשים סטנדרטיים.

זה מספק לנו מנגנונים ברמה נמוכה המיועדים בעיקר לשימוש פנימי בספריות הליבה.

2. השגת מופע של מְסוּכָּן

ראשית, להיות מסוגל להשתמש ב- מְסוּכָּן בכיתה, עלינו לקבל מופע - שאינו פשוט בהתחשב בכיתה שתוכננה רק לשימוש פנימי.

הדרך להשיג את המופע היא בשיטה הסטטית getUnsafe (). האזהרה היא שכברירת מחדל - זה יזרוק א ExceptionException.

לְמַרְבֶּה הַמַזָל, אנו יכולים להשיג את המופע באמצעות השתקפות:

שדה f = Unsafe.class.getDeclaredField ("theUnsafe"); f.setAccessible (נכון); לא בטוח = (לא בטוח) f.get (null);

3. הקמת שיעור באמצעות מְסוּכָּן

בואו נגיד שיש לנו מחלקה פשוטה עם קונסטרוקטור שקובע ערך משתנה בעת יצירת האובייקט:

Initialization class הזמנת {private long a; InitializationOrdering () {this.a = 1; } ציבורי ארוך getA () {להחזיר this.a; }}

כאשר אנו מאתחלים את האובייקט הזה באמצעות הבנאי, ה- לקבל() השיטה תחזיר ערך של 1:

InitializationOrdering o1 = new InitializationOrdering (); assertEquals (o1.getA (), 1);

אבל אנחנו יכולים להשתמש ב- allocateInstance () באמצעות שיטה מְסוּכָּן. זה רק יקצה את הזיכרון לשיעור שלנו ולא יפעיל קונסטרוקטור:

InitializationOrdering o3 = (InitializationOrdering) unsafe.allocateInstance (InitializationOrdering.class); assertEquals (o3.getA (), 0);

שים לב כי הקונסטרוקטור לא הוזעק ובשל עובדה זו, לקבל() השיטה החזירה את ערך ברירת המחדל עבור ה- ארוך סוג - שהוא 0.

4. שינוי שדות פרטיים

בואו נגיד שיש לנו שיעור שמקיים א סוֹד ערך פרטי:

מחלקה SecretHolder {private int SECRET_VALUE = 0; סוד בוליאני ציבוריIsDisclosed () {return SECRET_VALUE == 1; }}

משתמש ב putInt () שיטה מ מְסוּכָּן, אנו יכולים לשנות ערך של הפרטי SECRET_VALUE שדה, משנה / משחית את המצב של אותו מקרה:

SecretHolder secretHolder = SecretHolder חדש (); שדה f = secretHolder.getClass (). GetDeclaredField ("SECRET_VALUE"); unsafe.putInt (secretHolder, unsafe.objectFieldOffset (f), 1); assertTrue (secretHolder.secretIsDisclosed ());

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

5. השלכת חריג

הקוד שמופעל באמצעות מְסוּכָּן אינו נבחן באותו אופן על ידי המהדר כקוד Java רגיל. אנחנו יכולים להשתמש ב- throwException () שיטה לזרוק כל חריג מבלי להגביל את המתקשר לטפל בחריג זה, גם אם זה חריג מסומן:

@Test (צפוי = IOException.class) חלל ציבורי שניתןUnsafeThrowException_whenThrowCheckedException_thenNotNeedToCatchIt () {unsafe.throwException (IOException חדש ()); }

לאחר שזרק IOException, שנבדק, אנחנו לא צריכים לתפוס אותו ולא לציין אותו בהצהרת השיטה.

6. זיכרון מחוץ לערימה

אם ליישום נגמר זיכרון זמין ב- JVM, נוכל בסופו של דבר לאלץ את תהליך ה- GC לרוץ לעתים קרובות מדי. באופן אידיאלי, נרצה אזור זיכרון מיוחד, מחוץ לגיבוב ולא נשלט על ידי תהליך ה- GC.

ה allocateMemory () שיטה מה- מְסוּכָּן הכיתה נותנת לנו את היכולת להקצות חפצים ענקיים מהערמה, כלומר זיכרון זה לא נראה ונלקח בחשבון על ידי ה- GC ו- JVM.

זה יכול להיות שימושי מאוד, אך עלינו לזכור כי יש לנהל את הזיכרון הזה באופן ידני ולהשיב אותו כהלכה זיכרון חופשי() כשכבר אין צורך.

בואו נגיד שאנחנו רוצים ליצור את מערך הזיכרון הגדול של הבתים. אנחנו יכולים להשתמש ב- allocateMemory () שיטה להשיג זאת:

class OffHeapArray {private final static int BYTE = 1; גודל פרטי ארוך; כתובת פרטית ארוכה; OffHeapArray ציבורי (גודל ארוך) זורק NoSuchFieldException, IllegalAccessException {this.size = size; address = getUnsafe (). allocateMemory (גודל * BYTE); } פרטי לא בטוח GetUnsafe () זורק IllegalAccessException, NoSuchFieldException {שדה f = Unsafe.class.getDeclaredField ("theUnsafe"); f.setAccessible (נכון); return (לא בטוח) f.get (null); } מערך הריק הציבורי (ארוך i, ערך בתים) זורק NoSuchFieldException, IllegalAccessException {getUnsafe (). putByte (כתובת + i * BYTE, ערך); } ציבורי int get (idx ארוך) זורק NoSuchFieldException, IllegalAccessException {return getUnsafe (). getByte (כתובת + idx * BYTE); } גודל ארוך ציבורי () {גודל החזרה; } public void freeMemory () זורק NoSuchFieldException, IllegalAccessException {getUnsafe (). freeMemory (כתובת); }
}

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

לאחר מכן, אנו יכולים להקצות את מערך off-heap באמצעות הבנאי שלו:

ארוך SUPER_SIZE = (ארוך) שלם.MAX_VALUE * 2; מערך OffHeapArray = OffHeapArray חדש (SUPER_SIZE);

אנו יכולים להכניס מספרים N של ערכי בתים למערך זה ואז לאחזר את הערכים הללו, ולסכם אותם כדי לבדוק אם הכתובת שלנו עובדת כהלכה:

סכום int = 0; עבור (int i = 0; i <100; i ++) {array.set ((long) Integer.MAX_VALUE + i, (byte) 3); sum + = array.get ((long) Integer.MAX_VALUE + i); } assertEquals (array.size (), SUPER_SIZE); assertEquals (סכום, 300);

בסופו של דבר, עלינו לשחרר את הזיכרון למערכת ההפעלה על ידי התקשרות זיכרון חופשי().

7. CompareAndSwap מבצע

הקונסטרוקציות היעילות מאוד מה- java.concurrent חבילה, כמו AtomicInteger, משתמשים ב- CompareAndSwap () שיטות מתוך מְסוּכָּן מתחת, כדי לספק את הביצועים הטובים ביותר האפשריים. מבנה זה נמצא בשימוש נרחב באלגוריתמים ללא נעילה שיכולים למנף את הוראות מעבד ה- CAS כדי לספק מהירות רבה בהשוואה למנגנון הסנכרון הפסימאי הרגיל ב- Java.

אנו יכולים לבנות את הדלפק מבוסס ה- CAS באמצעות ה- CompareAndSwapLong () שיטה מ מְסוּכָּן:

Class CASCounter {פרטי לא בטוח לא בטוח; מונה ארוך נדיף פרטי = 0; קיזוז ארוך פרטי; פרטי לא בטוח GetUnsafe () זורק IllegalAccessException, NoSuchFieldException {שדה f = Unsafe.class.getDeclaredField ("theUnsafe"); f.setAccessible (נכון); return (לא בטוח) f.get (null); } CASCounter ציבורי () זורק Exception {unsafe = getUnsafe (); אופסט = unsafe.objectFieldOffset (CASCounter.class.getDeclaredField ("מונה")); } תוספת חלל ציבורית () {הרבה לפני = מונה; בעוד (! unsafe.compareAndSwapLong (זה, קיזוז, לפני, לפני + 1)) {before = counter; }} getCounter ארוך ציבורי () {דלפק החזרה; }}

בתוך ה CASCounter קונסטרוקטור אנו מקבלים את הכתובת של שדה הדלפק, כדי שנוכל להשתמש בה בהמשך תוֹסֶפֶת() שיטה. צריך להכריז על שדה זה כנדיף, להיות גלוי לכל הנושאים שכותבים וקוראים ערך זה. אנו משתמשים ב- objectFieldOffset () שיטה לקבלת כתובת הזיכרון של לְקַזֵז שדה.

החלק החשוב ביותר בשיעור זה הוא תוֹסֶפֶת() שיטה. אנו משתמשים ב- CompareAndSwapLong () בתוך ה בזמן לולאה כדי להגדיל את הערך שנאסף בעבר, לבדוק אם הערך הקודם השתנה מאז שהבאנו אותו.

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

אנו יכולים לבדוק את הקוד שלנו על ידי הגדלת הדלפק המשותף ממספר שרשורים:

int NUM_OF_THREADS = 1_000; int NUM_OF_INCREMENTS = 10_000; שירות ExecutorService = Executors.newFixedThreadPool (NUM_OF_THREADS); CASCounter casCounter = CASCounter חדש (); IntStream.rangeClosed (0, NUM_OF_THREADS - 1) .forEach (i -> service.submit (() -> IntStream .rangeClosed (0, NUM_OF_INCREMENTS - 1) .forEach (j -> casCounter.increment ())));

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

assertEquals (NUM_OF_INCREMENTS * NUM_OF_THREADS, casCounter.getCounter ());

8. פארק / לא מחנה

ישנן שתי שיטות מרתקות ב מְסוּכָּן ממשק API המשמש את ה- JVM להחלפת משנה. כאשר השרשור ממתין לפעולה כלשהי, ה- JVM יכול לחסום את השרשור הזה באמצעות ה- פָּארק() שיטה מה- מְסוּכָּן מעמד.

זה מאוד דומה ל Object.wait () שיטה, אך היא קוראת לקוד מערכת ההפעלה המקורית, ובכך לנצל כמה פרטים ארכיטקטוניים כדי להשיג את הביצועים הטובים ביותר.

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

9. מסקנה

במאמר זה, הסתכלנו על מְסוּכָּן הכיתה והמבנים השימושיים ביותר שלה.

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

ניתן למצוא את היישום של כל הדוגמאות וקטעי הקוד ב- GitHub - זהו פרויקט של Maven, כך שיהיה קל לייבא ולהפעיל אותו כפי שהוא.