JavaRush /בלוג Java /Random-HE /תרגום: 50 שאלות הראיון המובילות לפי שרשור. חלק 1.
KapChook
רָמָה
Volga

תרגום: 50 שאלות הראיון המובילות לפי שרשור. חלק 1.

פורסם בקבוצה
החלק הראשון של התרגום של המאמר המקורי 50 תשובות לשאלות ראיונות שרשורי ג'אווה מובילות למתכנתים טריים, מנוסים. חלק שני. הערה: התברר שהמאמר גדול, וזו הסיבה שהוא לא מתאים לנושא אחד. חוץ מזה, זה די מסובך, ניסיתי כמיטב יכולתי לחפש בגוגל, אבל בכל זאת. לכן, אנו מבקשים מהמשתתפים השולטים באנגלית להציץ במקור ולהשוות אותו לתרגום, למקרה שהם לא הבינו משהו או תרגמו אותו לא נכון. תודה מראש. בכל ראיון, בכיר או זוטר, מנוסה או מתחיל, אתה תעמוד בפני כמה שאלות על שרשורים, מקבילות וריבוי שרשורים. למעשה, תמיכה מובנית זו עבור מקביליות היא אחת מנקודות החוזק הגדולות ביותר של Java וסייעה לה להשיג פופולריות בקרב יזמים ומתכנתים כאחד. רוב המשרות הרווחיות של מפתחי Java דורשות מיומנויות מצוינות של ריבוי השחלות וניסיון בפיתוח, ניפוי באגים וכוונון יישומים בעלי ביצועים גבוהים, עם אחזור נמוך. לכן, זוהי אחת המיומנויות המבוקשות ביותר בראיונות. בראיון ג'אווה טיפוסי, המראיין מתחיל לאט לאט עם המושגים הבסיסיים של שרשורים, שואל שאלות כמו למה צריך שרשורים, איך ליצור אותם, איזו דרך ליצור אותם עדיפה, יורש מ-Thread או יישום Runnable, ואז לאט לאט עובר על קשיי המקבילות, קשיים שנתגלו בפיתוח יישומים מקבילים, כלי עזר ברמה גבוהה שהוצגו ב-JDK 1.5, עקרונות ודפוסי עיצוב של יישומים מקבילים, ובעיות ריבוי הליכים קלאסיות. מכיוון שלא מספיק לדעת רק את היסודות של ריבוי שרשורים, עליך לדעת כיצד להתמודד עם בעיות במקביל כמו מבוי סתום, תנאי מרוץ, חוסר עקביות בזיכרון ובעיות שונות של בטיחות חוטים. מיומנויות אלו נבדקות ביסודיות, ומציגות אתגרים שונים של ריבוי השחלות ובמקביל. מפתחי Java רבים בדרך כלל פשוט קוראים את השאלות לפני הראיון עצמו, וזה לא דבר רע, אבל אתה צריך להבין את זה. כמו כן, צבירת שאלות וביצוע אותם תרגילים מבזבזים הרבה זמן, אז יצרתי את הרשימה הזו.
  1. מה זה שרשור בג'אווה?

  2. שרשור הוא נתיב ביצוע עצמאי. המטרה שלו היא לנצל את המעבדים המרובים הזמינים במכונה. על ידי שימוש במספר שרשורים, אתה יכול להאיץ משימות הקשורות למעבד. לדוגמה, אם חוט אחד לוקח 100 מילישניות כדי להשלים עבודה, אתה יכול להשתמש ב-10 חוטים כדי לצמצם את העבודה ל-10 מילישניות. Java מספקת תמיכה מצוינת בריבוי שרשורים ברמת השפה וזה, יתר על כן, אחד היתרונות החזקים שלה.
  3. ההבדל בין שרשורים ותהליכים ב-Java?

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

  6. ברמת השפה, יש שתי דרכים ליצור שרשור. אובייקט של המחלקה java.lang.Thread מייצג שרשור, אך הוא דורש משימה להפעלה, שהיא אובייקט שמיישם את הממשק java.lang.Runnable. מכיוון שהמחלקה Thread מיישמת את ממשק Runnable, אתה יכול לעקוף את שיטת run() על ידי גזירת המחלקה שלך מ-Thread או על ידי הטמעת ממשק Runnable בה.
  7. מתי להשתמש ב- Runnable ומתי להשתמש ב-Thread?

  8. זו תוספת לשאלה הקודמת. כידוע, ניתן ליצור שרשור על ידי ירושה מהמחלקה Thread או על ידי יישום ממשק Runnable. נשאלת השאלה איזו שיטה עדיפה ומתי להשתמש באיזו? קל לענות על שאלה זו אם אתה יודע ש-Java אינה תומכת בירושה של מחלקות מרובות אך מאפשרת לך ליישם מספר ממשקים. מה שאומר שעדיף ליישם Runnable אם אתה רוצה לרשת ממחלקה אחרת.
  9. ההבדל בין שיטות start() ו-run()?

  10. אחת משאלות הטריק מהעבר, אבל היא עדיין מספיק טובה כדי להבחין בהבנה שטחית של ריבוי פתילים בג'אווה. השיטה start() משמשת לפתיחת שרשור חדש. למרות ש-start() קורא לשיטת run() באופן פנימי, זה לא זהה לפשוט לקרוא ל-run(). אם אתה קורא ל-run() כשיטה רגילה, הוא נקרא באותו שרשור ולא יתחיל שרשור חדש, וזה מה שקורה כשקוראים למתודה start().
  11. הבדלים בין ריצה לניתן להתקשר?

  12. שני הממשקים מייצגים משימות שנועדו להתבצע בשרשורים נפרדים. Runnable קיים מאז JDK 1.0, ו-Callable נוסף ב-JDK 1.5. ההבדל העיקרי הוא ששיטת ה-call() של Callable יכולה להחזיר ערכים ולזרוק חריגים, מה שלא אפשרי בשיטת run() של Runnable. ניתן להתקשר מחזירה אובייקט Future שיכול להכיל את התוצאה של חישוב.
  13. הבדלים בין CyclicBarrier ל-CountDownLatch?

  14. בעוד ששני הסינכרונים הללו מאפשרים לשרשורים להמתין זה לזה, ההבדל העיקרי ביניהם הוא שאינך יכול לעשות שימוש חוזר ב-CountDownLatch לאחר שהמונה שלו מגיע לאפס, אך אתה יכול להשתמש שוב ב-CyclicBarrier גם לאחר שהמחסום נשבר.
  15. מהו מודל הזיכרון של Java?

  16. מודל זיכרון הוא קבוצה של כללים וקווים מנחים המאפשרים לתוכניות Java לפעול באופן דטרמיניסטי על פני ארכיטקטורות זיכרון, מעבד ומערכות הפעלה מרובות. זה חשוב במיוחד במקרה של רב נימה. מודל הזיכרון מספק ערבויות לכך ששינויים שנעשו על ידי חוט אחד יהיו גלויים לאחרים, אחד מהם הוא הקשר קורה-לפני. קשר זה מגדיר מספר כללים המאפשרים למתכנתים לצפות ולקבוע את התנהגותן של תוכניות מקבילות. לדוגמה ערבויות קורה-לפני:
    • כל פעולה בשרשור מתרחשת לפני כל פעולה בשרשור זה שאחריה בסדר התוכנית, הידוע גם בשם כלל סדר התוכנית.
    • ביטול נעילה של צג מתרחש לפני כל נעילה של אותו צג, המכונה גם כלל נעילת הצג.
    • כתיבה לשדה נדיף מתרחשת לפני כל קריאה עוקבת של אותו שדה, כלל משתנה נדיף.
    • קריאה ל-Thread.start() בשרשור מתרחשת לפני שרשור אחר מבחין שהשרשור הופסק, לאחר שה-Thread.join() מצליח, או אם Thread.isAlive() מחזיר false, הכלל Thread.start() .
    • שרשור שנקטע על ידי שרשור אחר מתרחש לפני שהשרשור שנקטע מבחין בהפרעה (או מהשלכת InterruptedException או מבדיקה של isInterrupted()), כלל ההפרעה של השרשור.
    • הסוף של בנאי אובייקט מתרחש לפני תחילת ה-finalizer עבור אותו אובייקט, כלל Finalizer.
    • אם A קורה לפני B, ו-B קורה לפני C, אז A קורה לפני C, כלומר קורה-לפני מבטיח טרנזיטיביות.
  17. מהו משתנה נדיף?

  18. נדיף הוא משנה מיוחד שניתן להחיל רק על תכונות. בתוכניות Java מקבילות, שינויים שנעשו על ידי שרשורים שונים בתכונות אינם גלויים לאחרים בהיעדר סנכרון. משתנה נדיף מבטיח שכתיבה מתרחשת לפני קריאה שלאחר מכן, כפי שנאמר בכלל המשתנה נדיף בשאלה הקודמת.
  19. מהי בטיחות חוט? האם מחלקה וקטור בטוח?

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

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

  24. תמיד אמרתי ש-Java מספקת ממשקי API עשירים לכל דבר, אבל באופן אירוני לא מספקת דרכים נוחות לעצור שרשור. ל-JDK 1.0 היו מספר שיטות שליטה, כגון stop(), suspend() ו-resume(), שסומנו כמוצאה משימוש במהדורות עתידיות עקב איומי מבוי סתום; מאז, מפתחי Java API לא ניסו לספק שרשור -עמיד -דרך בטוחה ואלגנטית לעצור חוטים. מתכנתים מסתמכים בעיקר על העובדה שהשרשור עוצר את עצמו ברגע שהוא מסיים לבצע את שיטות run() או call(). כדי לעצור באופן ידני, מתכנתים מנצלים את המשתנה הבוליאני הנדיף ובודקים את ערכו בכל איטרציה אם יש לולאות בשיטת run() או קוטעים שרשורים בשיטת interrupt() כדי לבטל בפתאומיות משימות.
  25. מה קורה כששרשור זורק חריג?

  26. זו אחת משאלות הטריק הטובות האלה. במילים פשוטות, אם החריג לא נתפס, השרשור מת; אם מותקן מטפל לחריגים שלא נתפסו, הוא יקבל התקשרות חוזרת. Thread.UncaughtExceptionHandler הוא ממשק המוגדר כממשק מקונן עבור מטפלים הנקראים כאשר שרשור נעצר לפתע עקב חריג שלא נתפס. כאשר שרשור עומד למות עקב חריג שלא נתפס, ה-JVM יבדוק את נוכחותו של UncaughtExceptionHandler באמצעות Thread.getUncaughtExceptionHandler() ויקרא לשיטת uncaughtException() של המטפל, ויעביר את השרשור והחריג כארגומנטים.
  27. כיצד לשתף נתונים בין שני אשכולות?

  28. אתה יכול לשתף נתונים בין שרשורים באמצעות אובייקט משותף או מבני נתונים מקבילים כגון BlockingQueue.
  29. הבדלים בין notify לבין notifyAll?

  30. זו עוד שאלה מסובכת, מכיוון שניתן לנטר מוניטור אחד על ידי שרשורים מרובים, מפתחי Java API מספקים שיטה להודיע ​​רק על אחד או כל השרשורים על שינוי במצבו, אבל הם מספקים רק חצי מהיישום. למתודה notify() אין דרך לבחור שרשור ספציפי, לכן היא שימושית רק כאשר אתה יודע בוודאות שרק שרשור אחד ממתין. מצד שני, notifyAll() מודיע לכל השרשורים ומאפשר להם להתחרות על המוניטור, מה שמבטיח שלפחות שרשור אחד מתקדם.
  31. מדוע wait, notify and notifyAll אינם בכיתה Thread?

  32. זו שאלה עיצובית שבודקת מה חושב המועמד על מערכות קיימות או האם אי פעם חשבו על משהו דומה שנראה לא במקום בהתחלה. כדי לענות על שאלה זו, עליך לספק מספר סיבות מדוע שיטות אלו מיושמות טוב יותר במחלקה Object, ומדוע לא במחלקה Thread. הסיבה הברורה הראשונה היא ש-Java תומכת במנעולים ברמת האובייקט, לא ברמת ה-thread. לכל חפץ יש מנעול, הנרכש על ידי חוט. ואם שרשור צריך להמתין למנעול מסוים, הגיוני יותר לקרוא ל-wait() על אובייקט מאשר על השרשור הזה. אם wait() הוכרז במחלקה Thread, לא יהיה ברור על איזה מנעול חיכה השרשור. בקיצור, מכיוון ש- wait, notify ו- notifyAll פועלים ברמת הנעילה, נוח יותר להכריז עליהם במחלקה Object כי lock מתייחס לאובייקט.
  33. מהו משתנה ThreadLocal?

  34. משתני ThreadLocal הם סוג מיוחד של משתנים הזמינים למתכנת Java. בדיוק כפי שלמצבים יש משתנה מצב, ל-threads יש משתני ThreadLocal. זוהי דרך טובה להשיג בטיחות שרשור עבור אובייקטים יקרים ליצירה; לדוגמה, אתה יכול להפוך את SimpleDateFormat לבטוח שרשור באמצעות ThreadLocal. מכיוון שמדובר במחלקה יקרה, לא כדאי להשתמש בה בהיקף מקומי הדורש מופעים נפרדים לכל שיחה. על ידי מתן כל חוט עותק משלו, אתה הורג שתי ציפורים במכה אחת. ראשית, אתה מפחית את מספר המופעים של אובייקטים יקרים על ידי שימוש במספר קבוע חדש של מופעים, ושנית, אתה משיג בטיחות חוטים מבלי לאבד סנכרון ואי-שינוי. דוגמה טובה נוספת למשתנה Thread-local היא המחלקה ThreadLocalRandom, אשר מפחיתה את מספר המקרים של אובייקטים אקראיים יקרים ליצירה בסביבה מרובת הליכי שרשור.
  35. מה זה FutureTask?

  36. FutureTask הוא חישוב אסינכרוני שניתן לביטול באפליקציית Java מקבילה. מחלקה זו מספקת יישום Future בסיסי, עם שיטות להתחלה ועצירה של חישוב, שיטות לשאילתה על מצב החישוב ושליפה של תוצאות. ניתן לקבל את התוצאה רק לאחר השלמת החישוב; שיטת getter תחסם אם החישוב טרם הושלם. ניתן להשתמש באובייקטים של FutureTask כדי לעטוף אובייקטים הניתנים להתקשרות ולהפעלה. מכיוון ש-FutureTask מיישמת Runnable, ניתן להעביר אותו ל-Executor לביצוע.
  37. ההבדל בין interrupted לבין isinterrupted?

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

  40. הסיבה העיקרית להתקשר להמתין ולהודיע ​​מבלוק או מתודה סטטית היא ש-Java API דורש זאת. אם תתקשר אליהם מבחוץ לחסימה מסונכרנת, הקוד שלך יזרוק הודעה של IllegalMonitorStateException. סיבה חכמה יותר היא להימנע ממצב מירוץ בין המתנה לשיחות הודעה.
  41. למה כדאי לבדוק את מצב ההמתנה בלולאה?

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

  44. בעוד שגם אוספים מסונכרנים וגם אוספים בו-זמניים מספקים אוספים בטוחים בשרשור, זה האחרון ניתן להרחבה יותר. לפני Java 1.5, למתכנתים הייתה גישה רק לאוספים מסונכרנים, שהפכו למקור מחלוקת כאשר שרשורים מרובים ניגשו אליהם בו-זמנית, מה שהקשה על קנה המידה של המערכת. Java 5 הציגה אוספים מקבילים כגון ConcurrentHashMap, אשר לא רק מספקים בטיחות חוטים אלא גם משפרים את יכולת ההרחבה תוך שימוש בטכניקות מודרניות כגון הפשטת מנעולים וחלוקת שולחן פנימית.
  45. הבדלים בין Stack ל-Heap?

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

  48. יצירת שרשור יקרה מבחינת זמן ומשאבים. אם אתה יוצר שרשור בזמן שבקשה בעיבוד, זה יאט את זמן התגובה, והתהליך יכול ליצור רק מספר מוגבל של שרשורים. כדי להימנע מבעיות אלו, נוצר מאגר של שרשורים בעת הפעלת האפליקציה ומשתמשים בשרשורים חוזרים לעיבוד בקשות. בריכת החוטים הזו נקראת "בריכת החוטים", והחוטים בה נקראים חוט הפועל. החל מ-Java 1.5, ה-API של Java מספק את מסגרת ה-Executor, המאפשרת ליצור מאגר שרשורים שונים, כמו מאגר שרשורים בודד, המעבד רק עבודה אחת ליחידת זמן, מאגר שרשורים קבוע, מאגר עם מספר קבוע של פתילים, ומאגר פתילים שמור במטמון, בריכה הניתנת להרחבה.מתאים ליישומים עם הרבה משימות קצרות מועד.
  49. כיצד לפתור את בעיית הצרכן המפיק?

  50. רוב בעיות החוט שאתה פותר במציאות הן מדפוס ה-Producer Consumer, שבו חוט אחד יוצר את הבעיה והשני מכלה אותה. אתה צריך לדעת איך לבנות אינטראקציות חוט פנימיות כדי לפתור בעיה זו. ברמה נמוכה אתה יכול לנצל את שיטות ההמתנה וההתראה, וברמה גבוהה אתה יכול לנצל את ה-Semaphore או BlockingQueue
  51. איך למנוע מבוי סתום?

  52. תרגום: 50 שאלות הראיון המובילות לפי שרשור.  חלק 1. - 1 מבוי סתום הוא מצב שבו שרשור ממתין לשרשור השני לבצע פעולה כלשהי, והשני, בו זמנית, מחכה לאותה חוט מהראשון. זו בעיה חמורה מאוד שגורמת לתוכנית שלך לקפוא ולא לעשות את מה שהיא נועדה לעשות. מבוי סתום מתרחש כאשר מגיעים לארבעת המצבים הבאים:
    • אי הכללה הדדית: לפחות משאב אחד חייב להיות תפוס במצב בלתי ניתן לחלוקה. רק תהליך אחד יכול להשתמש במשאב בכל זמן נתון.
    • החזק והמתן: תהליך מחזיק לפחות משאב אחד ומבקש משאבים נוספים המוחזקים על ידי תהליכים אחרים.
    • אין ניקוי מקדים: מערכת ההפעלה אינה מקצה מחדש משאבים אם הם כבר תפוסים, יש לתת אותם מרצון לתהליך ההחזקה.
    • המתנה מחזורית: תהליך ממתין למשאבים שישוחררו על ידי תהליך אחר, שבתורו ממתין לשחרור המשאבים בתהליך הראשון.
    הדרך הפשוטה ביותר למנוע מבוי סתום היא להימנע מהמתנה בלולאה; ניתן להשיג זאת על ידי רכישת מנעולים בסדר מסוים ושחרורם בסדר הפוך.
  53. הבדלים בין חיסכון למבוי סתום?

  54. לייבלוק דומה למבוי סתום, רק ב-livelock מצבי החוטים או התהליכים המעורבים משתנים כל הזמן בהתאם זה לזה. Livelock הוא מקרה מיוחד של מחסור במשאבים. דוגמה אמיתית ל-livelock היא כאשר שני אנשים נפגשים במסדרון צר וכל אחד, מנסה להיות מנומס, צועד הצידה, וכך הם זזים ללא סוף מצד לצד.
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION