שאלות על ראיונות במקביל ל- Java (+ תשובות)

מאמר זה הוא חלק מסדרה: • שאלות ראיונות בנושא אוספי Java

• שאלות בנושא ראיונות מערכת מסוג Java

• שאלות על ראיונות במקביל ל- Java (+ תשובות) (מאמר נוכחי) • שאלות על ראיונות מבנה כיתת Java ו- Initialization

• Java 8 שאלות ראיונות (+ תשובות)

• ניהול זיכרון בשאלות ראיון עם Java (+ תשובות)

• שאלות ראיונות עם Java Generics (+ תשובות)

• שאלות ראיונות עם בקרת זרימת Java (+ תשובות)

• שאלות על ראיונות חריגים עם Java (+ תשובות)

• שאלות ראיונות בהערות Java (+ תשובות)

• שאלות על ראיונות מסגרת האביב המובילה

1. הקדמה

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

שאלה 1. מה ההבדל בין תהליך לחוט?

שני התהליכים והשרשורים הם יחידות של מקבילות, אך יש להם הבדל מהותי: תהליכים אינם חולקים זיכרון משותף, ואילו שרשורים כן.

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

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

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

שאלה 2. איך אתה יכול ליצור מופע שרשור ולהריץ אותו?

כדי ליצור מופע של שרשור, יש לך שתי אפשרויות. ראשית, העבירו א ניתן לרוץ למשל לבנאי שלו ולהתקשר אליו הַתחָלָה(). ניתן לרוץ הוא ממשק פונקציונלי, כך שניתן להעבירו כביטוי למבדה:

חוט משנה 1 = שרשור חדש (() -> System.out.println ("שלום עולם מניתן לרוץ!"); thread1.start ();

החוט מיישם גם ניתן לרוץ, כך שדרך אחרת להתחיל שרשור היא ליצור תת-מחלקה אנונימית, לעקוף אותה לָרוּץ() שיטה ואז להתקשר הַתחָלָה():

אשכול 2 = שרשור חדש () {@Override public void run () {System.out.println ("שלום עולם מתת-מחלקה!"); }}; thread2.start ();

שאלה 3. תאר את מצבי החוט השונים ומתי מתרחשים מעברי המדינה.

מצבו של א פְּתִיל ניתן לבדוק באמצעות Thread.getState () שיטה. מצבים שונים של א פְּתִיל מתוארים ב חוט. מדינה enum. הם:

  • חָדָשׁ - חדש פְּתִיל מופע שעדיין לא התחיל באמצעות Thread.start ()
  • לרוץ - חוט רץ. זה נקרא runnable מכיוון שבכל זמן נתון זה יכול לרוץ או לחכות לקוואנט הזמן הבא מתזמון החוטים. א חָדָשׁ חוט נכנס ל לרוץ ציין מתי אתה מתקשר Thread.start () על זה
  • חָסוּם - שרשור פועל נחסם אם עליו להזין קטע מסונכרן אך אינו יכול לעשות זאת עקב שרשור אחר המחזיק את הצג של החלק הזה.
  • הַמתָנָה - חוט נכנס למצב זה אם הוא ממתין לשרשור אחר שיבצע פעולה מסוימת. לדוגמא, חוט נכנס למצב זה בעת קריאת ה- Object.wait () שיטה על צג שהוא מחזיק, או Thread.join () שיטה על חוט אחר
  • TIMED_WAITING - זהה לאמור לעיל, אך שרשור נכנס למצב זה לאחר קריאת גרסאות מתוזמנות של Thread.sleep (), Object.wait (), Thread.join () וכמה שיטות אחרות
  • הופסק - חוט השלים את ביצועו Runnable.run () שיטה והסתיים

שאלה 4. מה ההבדל בין הממשקים הניתנים להפעלה וניתנים להתקשרות? איך משתמשים בהם?

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

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

ש 5. מהו חוט דיימון, מהם מקרי השימוש בו? כיצד ניתן ליצור חוט דמון?

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

כדי להפעיל שרשור כדמון, עליך להשתמש ב- setDaemon () שיטה לפני שיחה הַתחָלָה():

חוט הדמון = שרשור חדש (() -> System.out.println ("שלום מדמון!"); daemon.setDaemon (נכון); daemon.start ();

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

שאלה 6. מהו דגל ההפרעה של החוט? איך אתה יכול להגדיר ולבדוק את זה? איך זה קשור לתפיסת ההפרעה?

דגל ההפרעה, או מעמד ההפרעה, הוא פנימי פְּתִיל דגל שמוגדר בעת הפסקת החוט. כדי להגדיר את זה, פשוט התקשרו thread.interrupt () על אובייקט החוט.

אם חוט נמצא כרגע באחת השיטות שזורקות חריג מופרע (לַחֲכוֹת, לְהִצְטַרֵף, לִישׁוֹן וכו '), אז שיטה זו זורקת מיד את InterruptedException. השרשור חופשי לעבד חריג זה על פי ההיגיון שלו.

אם חוט אינו נמצא בשיטה כזו ו thread.interrupt () נקרא, שום דבר מיוחד לא קורה. באחריות השרשור לבדוק מעת לעת את מצב ההפרעה באמצעות Thread.interrupted () או מופע isInterrupted () שיטה. ההבדל בין שיטות אלה הוא שה- Thread thread.interrupted () מנקה את דגל ההפרעה, תוך כדי isInterrupted () לא.

ש 7. מה הם מוציאים לשירות ומוציאים לפועל? מהם ההבדלים בין ממשקים אלה?

מוציא להורג ו שירות ExecutorService הם שני ממשקים קשורים של java.util.concurrent מִסגֶרֶת. מוציא להורג הוא ממשק פשוט מאוד עם יחיד לבצע קבלת שיטה ניתן לרוץ מקרים לביצוע. ברוב המקרים זהו הממשק שעל קוד ביצוע המשימות שלך להיות תלוי בו.

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

למידע נוסף על השימוש מוציא להורג ו שירות ExecutorService, עיין במאמר מדריך ל- Java ExecutorService.

ש 8. מהן היישומים הזמינים של שירות ההוצאה לפועל בספריה הסטנדרטית?

ה שירות ExecutorService לממשק שלוש יישומים סטנדרטיים:

  • ThreadPoolExecutor - לביצוע משימות באמצעות מאגר שרשורים. לאחר סיום החוט שמבצע את המשימה, הוא חוזר לבריכה. אם כל האשכולות בבריכה תפוסים, אז על המשימה להמתין לתור.
  • ScheduledThreadPoolExecutor מאפשר לתזמן את ביצוע המשימות במקום להריץ אותה מיד כאשר שרשור זמין. זה יכול גם לתזמן משימות עם תעריף קבוע או עיכוב קבוע.
  • ForkJoinPool הוא מיוחד שירות ExecutorService להתמודדות עם משימות אלגוריתמים רקורסיביות. אם אתה משתמש בחומר רגיל ThreadPoolExecutor עבור אלגוריתם רקורסיבי, תמצא במהירות שכל האשכולות שלך עסוקים בהמתנה לסיום הרמות הנמוכות יותר. ה ForkJoinPool מיישם את האלגוריתם כביכול גניבת עבודה המאפשר לו להשתמש בפתילים זמינים בצורה יעילה יותר.

שאלה 9. מהו דגם זיכרון Java (Jmm)? תאר את מטרתו ורעיונותיו הבסיסיים.

מודל Java Memory הוא חלק ממפרט השפה של Java המתואר בפרק 17.4. הוא מציין כיצד מספר שרשורים ניגשים לזיכרון משותף ביישום Java בו-זמנית, וכיצד שינויים בנתונים באמצעות שרשור אחד גלויים לשרשורים אחרים. למרות שהיא די קצרה ותמציתית, JMM עשוי להיות קשה לתפוס ללא רקע מתמטי חזק.

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

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

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

הרעיונות העיקריים של JMM הם:

  • פעולות, אלה פעולות בין חוטים שניתן לבצע על ידי שרשור אחד ולזהות על ידי שרשור אחר, כמו קריאה או כתיבה של משתנים, נעילת / נעילת צגים וכן הלאה
  • פעולות סינכרון, תת קבוצה מסוימת של פעולות, כמו קריאה / כתיבה א נָדִיף משתנה, או נעילה / נעילה של צג
  • סדר התוכנית (PO), סדר הפעולות הכולל הנצפה בתוך חוט יחיד
  • צו סינכרון (SO), הסדר הכולל בין כל פעולות הסנכרון - זה צריך להיות עקבי עם סדר התוכנית, כלומר אם שתי פעולות סנכרון באות זו בזו ב- PO, הן מתרחשות באותו סדר ב- SO
  • מסנכרן-עם (SW) קשר בין פעולות סינכרון מסוימות, כמו ביטול נעילה של צג ונעילה של אותו צג (בשרשור אחר או באותו)
  • קורה לפני ההזמנה - משלב PO עם SW (זה נקרא סגירה מעבר בתורת הקבוצות) כדי ליצור סידור חלקי של כל הפעולות בין השרשור. אם פעולה אחת קורה-לפני אחרת, ואז ניתן לראות את התוצאות של הפעולה הראשונה על ידי הפעולה השנייה (למשל, כתוב על משתנה בשרשור אחד וקרא באחרת)
  • קורה לפני עקביות - קבוצת פעולות תואמת את HB אם כל קריאה מתבוננת בכתיבה האחרונה למיקום זה בסדר שקורה לפני, או בכתיבה אחרת באמצעות מירוץ נתונים
  • ביצוע - קבוצה מסוימת של פעולות מסודרות וכללי עקביות ביניהן

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

ש 10. מהו שדה נדיף ומה התחייבות ה- Jmm לתחום כזה?

א נָדִיף לשדה יש ​​מאפיינים מיוחדים על פי מודל הזיכרון של Java (ראה שאלה 9). הקריאות והכתיבה של א נָדִיף משתנה הם פעולות סנכרון, כלומר יש להם סידור כולל (כל האשכולות יראו סדר עקבי של פעולות אלה). קריאה של משתנה נדיף מובטחת שתבחין בכתיבה האחרונה למשתנה זה, על פי סדר זה.

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

ערבות נוספת ל נָדִיף היא אטומיות של כתיבה וקריאה של ערכי 64 סיביות (ארוך ו לְהַכפִּיל). ללא שינוי נדיף, קריאה של שדה כזה יכולה להתבונן בערך שנכתב בחלקו על ידי שרשור אחר.

שאלה 11. איזו מהפעולות הבאות אטומיות?

  • כותב ללאנָדִיףint;
  • כותב לא תנודתי נדיף;
  • כותב לאדם שאינותנודתי ארוך;
  • כותב לא תנודתי ארוך;
  • תוספת א תנודתי ארוך?

כתיבה ל- int משתנה (32 סיביות) מובטח שהוא אטומי, בין אם הוא נָדִיף או שלא. א ארוך משתנה (64 סיביות) יכול להיות כתוב בשני שלבים נפרדים, למשל, על ארכיטקטורות של 32 סיביות, כך שכברירת מחדל, אין ערבות אטומיות. עם זאת, אם אתה מציין את נָדִיף משנה, א ארוך מובטח שניתן יהיה לגשת אליו משתנה.

פעולת התוספת מתבצעת בדרך כלל במספר שלבים (אחזור ערך, שינוי זה וכתיבה חזרה), כך שלעולם לא מובטח שהוא יהיה אטומי, אם המשתנה הוא נָדִיף או שלא. אם אתה צריך ליישם תוספת אטומית של ערך, עליך להשתמש בכיתות AtomicInteger, AtomicLong וכו '

שאלה 12. אילו ערבויות מיוחדות מחזיק ה- Jmm בשדות אחרונים בכיתה?

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

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

ש 13. מה המשמעות של מילת מפתח מסונכרנת בהגדרת שיטה? של שיטה סטטית? לפני חסימה?

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

מסונכרן (אובייקט) {// ...}

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

מופע ריק מסונכרן Methode () {// ...}

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

סטטי מסונכרן ריק ריק staticMethod () {// ...}

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

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

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

ש 15. מה מטרת שיטות ההמתנה, ההודעה וההודעה על כל מעמד האובייקט?

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

כאשר חוט אחר שרכש את הצג ממלא את התנאי, הוא עשוי להתקשר object.notify () אוֹ object.notifyAll () ושחרר את הצג. ה לְהוֹדִיעַ השיטה מעירה חוט יחיד במצב ההמתנה, וה- להודיע ​​הכל השיטה מעירה את כל הנושאים שמחכים למוניטור זה, וכולם מתחרים על רכישת המנעול מחדש.

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

מחלקה ציבורית BlockingQueue {תור לרשימה פרטית = LinkedList חדש (); מגבלת int פרטי = 10; put ריק מסונכרן פומבי (פריט T) {while (queue.size () == limit) {try {wait (); } לתפוס (InterruptedException e) {}} if (queue.isEmpty ()) {notifyAll (); } queue.add (פריט); } T מסונכרן ציבורי T () זורק את InterruptedException {בזמן (queue.isEmpty ()) {נסה {wait (); } לתפוס (InterruptedException e) {}} if (queue.size () == limit) {notifyAll (); } חזור לתור.הסר (0); }}

ש 16. תאר את התנאים של מבוי סתום, Livelock והרעבה. תאר את הסיבות האפשריות לתנאים אלה.

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

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

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

ש 17. תאר את מטרת השימוש במקרים של מזלג / הצטרף למסגרת.

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

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

מידע נוסף ודוגמאות קוד עבור מסגרת המזלג / הצטרפות ניתן למצוא במאמר "מדריך למזלג / הצטרף למסגרת ב- Java".

הַבָּא » שאלות על ראיונות מבנה כיתת Java ו אתחול « שאלות קודמות על ראיונות מערכת מסוג Java