ההבדל בין חוט לחוט וירטואלי בג'אווה

1. הקדמה

במדריך זה נראה את ההבדל בין שרשורים מסורתיים בג'אווה לחוטים הווירטואליים שהוצגו ב- Project Loom.

לאחר מכן, נשתף בכמה מקרים של שימוש בשרשורים וירטואליים ובממשקי ה- API שהפרויקט הציג.

לפני שנתחיל, עלינו לציין פרויקט זה נמצא בפיתוח פעיל. נפעיל את הדוגמאות שלנו על VM נול מוקדם: openjdk-15-loom + 4-55_windows-x64_bin.

גרסאות חדשות יותר של ה- build מאפשרות לשנות ולשבור את ממשקי ה- API הנוכחיים. עם זאת, כבר היה שינוי משמעותי ב- API, כפי שהיה בעבר בשימוש java.lang.Fiber המחלקה הוסרה והוחלפה בחדשה java.lang.VirtualThread מעמד.

2. סקירה ברמה גבוהה של חוט לעומת חוט וירטואלי

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

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

ואז, בדרך כלל, אנחנו לא רוצים לחסום את האשכולות הללו, וכתוצאה מכך השימושים בממשקי API של I / O ו- API אסינכרוני שאינם חוסמים, העלולים להעמיס על הקוד שלנו.

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

לאחר מכן, מאפיין מרכזי של שרשורים וירטואליים הוא שהם אינם חוסמים את שרשור הספק שלנו. עם זאת, חסימת שרשור וירטואלי הופכת לפעולה זולה בהרבה, מכיוון שה- JVM יתזמן חוט וירטואלי נוסף, וישאיר את שרשור הספק חסום.

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

3. API חדש לבניית אשכולות

ב- Loom קיבלנו את ה- API החדש לבנייה ב- פְּתִיל בכיתה, יחד עם מספר שיטות מפעל. בואו נראה איך נוכל ליצור מפעלים סטנדרטיים ווירטואליים ולהשתמש בהם לביצוע החוטים שלנו:

ניתן להפעיל את printThread = () -> System.out.println (Thread.currentThread ()); ThreadFactory virtualThreadFactory = Thread.builder (). וירטואלי (). מפעל (); ThreadFactory kernelThreadFactory = Thread.builder (). מפעל (); שרשור virtualThread = virtualThreadFactory.newThread (printThread); חוט kernelThread = kernelThreadFactory.newThread (printThread); virtualThread.start (); kernelThread.start ();

הנה התפוקה של הריצה לעיל:

חוט [חוט -0,5, ראשי] VirtualThread [, ForkJoinPool-1-worker-3, CarrierThreads]

כאן, הערך הראשון הוא הסטנדרט toString פלט של חוט הליבה.

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

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

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

4. הרכב חוטים וירטואלי

זה המשך ומתזמן שמהווים יחד חוט וירטואלי. כעת, מתזמן מצב המשתמש שלנו עשוי להיות כל יישום של ה- מוציא להורג מִמְשָׁק. הדוגמה לעיל הראתה לנו כי כברירת מחדל, אנו פועלים על ForkJoinPool.

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

שים לב שההמשך הוא ממשק API ברמה נמוכה, וכי על מתכנתים להשתמש בממשקי API ברמה גבוהה יותר כמו ה- API של בונה להפעלת שרשורים וירטואליים.

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

var scope = חדש ContinuationScope ("C1"); var c = המשך חדש (היקף, () -> {System.out.println ("התחל C1"); המשך.תשואה (היקף); System.out.println ("סוף C1");}); בעוד (! c.isDone ()) {System.out.println ("התחל לרוץ ()"); c.run (); System.out.println ("סוף הפעלה ()"); }

הנה התפוקה של הריצה לעיל:

התחל ריצה () התחל C1 סיום ריצה () התחל ריצה () סיום C1 סיום ריצה ()

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

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

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

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

5. מסקנה

במאמר זה דנו בהבדל בין שרשור הליבה לשרשור הווירטואלי. לאחר מכן, הראינו כיצד נוכל להשתמש ב- API חדש לבניית פתילים מ- Project Loom להפעלת השרשור הווירטואלי.

לבסוף הראינו מהו המשך וכיצד הוא פועל מתחת למכסה המנוע. אנו יכולים לחקור את מצב Project Loom על ידי בדיקת ה- VM עם גישה מוקדמת. לחלופין, אנו יכולים לחקור עוד ממשקי ה- API המקבילים של Java במקביל.


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