JavaRush /בלוג Java /Random-HE /ניתוח שאלות ותשובות מראיונות למפתח Java. חלק 12

ניתוח שאלות ותשובות מראיונות למפתח Java. חלק 12

פורסם בקבוצה
שלום! ידע הוא כוח. ככל שיהיה לך יותר ידע לפני הראיון הראשון שלך, כך תרגיש יותר בטוח בו. ניתוח שאלות ותשובות מראיונות למפתח Java.  חלק 12 - 1עם כמות טובה של ידע, יהיה לך קשה לבלבל, ובמקביל, תוכל להפתיע לטובה את המראיין שלך. לכן, היום, ללא עיכובים נוספים, נמשיך לחזק את הבסיס התיאורטי שלך על ידי בחינת 250+ שאלות עבור מפתח Java . ניתוח שאלות ותשובות מראיונות למפתח Java.  חלק 12 - 2

103. מהם הכללים לבדיקת חריגים בירושה?

אם אני מבין נכון את השאלה, הם שואלים על הכללים לעבודה עם חריגים במהלך הירושה, והם כדלקמן:
  • שיטה שדרסה או מיושמת בצאצא/יישום לא יכולה לזרוק חריגים מסומנים שהם גבוהים יותר בהיררכיה מהחריגים בשיטת superclass/interface.
כלומר, אם יש לנו ממשק מסוג Animal עם שיטה שזורקת את IOException :
public  interface Animal {
   void voice() throws IOException;
}
ביישום של ממשק זה, איננו יכולים לזרוק חריג נזרק כללי יותר (לדוגמה, Exception , Throwable ), אך אנו יכולים להחליף אותו בחריג צאצא, כגון FileNotFoundException :
public class Cat implements Animal {
   @Override
   public void voice() throws FileNotFoundException {
// некоторая реализация
   }
}
  • בנאי המשנה חייב לכלול בבלוק ההשלכות שלו את כל מחלקות החריגות שנזרקו על ידי בנאי מחלקות העל שנקרא בעת יצירת האובייקט.
נניח שהקונסטרוקטור של מחלקת החיות זורק הרבה חריגים:
public class Animal {
  public Animal() throws ArithmeticException, NullPointerException, IOException {
  }
אז יורש הכיתה חייב לציין אותם גם בקונסטרוקטור:
public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
או, כמו במקרה של שיטות, אתה יכול לציין לא את אותם חריגים, אלא כלליים יותר. במקרה שלנו, די יהיה לציין חריג כללי יותר - חריג , שכן זהו האב הקדמון המשותף של כל שלושת החריגים הנחשבים:
public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. האם תוכל לכתוב קוד מתי החסימה הסופית לא תבוצע?

ראשית, בואו נזכור מה זה סוף סוף . בעבר, בדקנו את המנגנון לתפיסת חריגים: בלוק try מתאר את אזור התפיסה, בעוד שבלוק ה- catch (ים) הוא הקוד שיעבוד כאשר נזרק חריג ספציפי. לבסוף הוא גוש הקוד השלישי לאחר שלבסוף שניתן להחלפה ב- catch אך אינו מונע זה מזה. המהות של הבלוק הזה היא שהקוד שבו תמיד עובד, ללא קשר לתוצאה של הנסיון או התפיסה (ללא קשר אם נזרק חריג או לא). מקרים של כישלון שלו הם נדירים מאוד והם חריגים. המקרה הפשוט ביותר של כשל הוא כאשר השיטה System.exit(0) נקראת בקוד שלמעלה , מה שמסיים את התוכנית (מכבה אותה):
try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("Данное сообщение не будет выведенно в консоль");
}
יש גם כמה מצבים אחרים שבהם לבסוף לא יעבוד:
  • סיום חריג של התוכנית שנגרם כתוצאה מבעיות קריטיות במערכת, או נפילה של שגיאה כלשהי ש"תקרוס" את האפליקציה (דוגמה לשגיאה יכולה להיות אותה StackOwerflowError שמתרחשת כאשר זיכרון המחסנית עולה על גדותיו).
  • כשחוט השד עובר דרך ה-ry...סוף סוף בלוק ובמקביל לזה התוכנית מסתיימת. הרי שרשור deamon הוא שרשור לפעולות רקע, כלומר אינו בעדיפות וחובה, והאפליקציה לא תחכה לסיום עבודתה.
  • הלולאה האינסופית הנפוצה ביותר, ב- try or catch , פעם אחת שבה הזרימה תישאר שם לנצח:

    try {
       while (true) {
       }
    } finally {
       System.out.println("Данное сообщение не будет выведенно в консоль");
    }

שאלה זו פופולרית למדי בראיונות למתחילים, כך שכדאי לזכור כמה מהמצבים החריגים הללו. ניתוח שאלות ותשובות מראיונות למפתח Java.  חלק 12 - 3

105. כתוב דוגמה לטיפול במספר חריגים בגוש תפס אחד

1) אולי השאלה נשאלה בצורה לא נכונה. למיטב הבנתי, שאלה זו פירושה מספר תפיסות עבור בלוק ניסיון אחד :
try {
  throw new FileNotFoundException();
} catch (FileNotFoundException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (IOException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (Exception e) {
   System.out.print("Упс, у вас упало исключение - " + e);
}
אם מתרחש חריג בבלוק ניסיון , אז בלוקי ה-catch מנסים לסירוגין לתפוס אותו מלמעלה למטה. אם בלוק תפס מסוים מצליח , הוא מקבל את הזכות לטפל בחריגה, בעוד ששאר הבלוקים למטה לא יהיו עוד מסוגלים לנסות לתפוס אותו ולעבד אותו בדרכם שלהם. לכן, חריגים צרים יותר ממוקמים גבוה יותר בשרשרת ה-catch block, וחריגים רחבים יותר ממוקמים נמוך יותר. לדוגמה, אם בבלוק ה-catch הראשון שלנו נתפס חריג מהמחלקה Exception , אז חריגים מסומנים לא יוכלו להיכנס לבלוקים הנותרים (הבלוקים הנותרים עם צאצאים של Exception יהיו חסרי תועלת לחלוטין). 2) השאלה נשאלה בצורה נכונה. במקרה זה, העיבוד שלנו ייראה כך:
try {
  throw new NullPointerException();
} catch (Exception e) {
   if (e instanceof FileNotFoundException) {
       // некоторая обработка с сужением типа (FileNotFoundException)e
   } else if (e instanceof ArithmeticException) {
       // некоторая обработка с сужением типа (ArithmeticException)e
   } else if(e instanceof NullPointerException) {
       // некоторая обработка с сужением типа (NullPointerException)e
   }
לאחר שתפסנו חריג דרך catch , אנו מנסים לברר את הסוג הספציפי שלו באמצעות instanceof method , המשמשת לבדיקת האם אובייקט שייך לסוג מסוים, כך שבהמשך נוכל לצמצם אותו לסוג זה ללא השלכות שליליות. ניתן להשתמש בשתי הגישות הנחשבות באותו מצב, אבל אמרתי שהשאלה לא נכונה כי לא הייתי קורא לאפשרות השנייה טובה ומעולם לא ראיתי אותה בתרגול שלי, כשבמקביל השיטה הראשונה עם ריבוי תפיסות זכתה לתפוצה רחבה תשומת לב. התפשטות. ניתוח שאלות ותשובות מראיונות למפתח Java.  חלק 12 - 4

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

כבר השתמשתי בו כמה פעמים למעלה, אבל בכל זאת אחזור על מילת המפתח הזו - זרוק . שימוש לדוגמה (כפיית חריגה):
throw new NullPointerException();

107. האם השיטה הראשית יכולה לזרוק חריג זריקות? אם כן, לאן זה יועבר?

קודם כל, אני רוצה לציין ש- main היא לא יותר משיטה רגילה, וכן, היא נקראת על ידי המכונה הוירטואלית כדי להתחיל להפעיל את התוכנית, אבל חוץ מזה, ניתן לקרוא לה מכל קוד אחר. כלומר, הוא גם כפוף לכללים הרגילים לציון חריגים מסומנים לאחר זריקות :
public static void main(String[] args) throws IOException {
בהתאם לכך, עלולים להתרחש בו גם חריגים. אם main לא נקרא בשיטה כלשהי, אלא הופעל כנקודת השקת תוכנית, אז החריג שנזרק על ידה יטופל על ידי המיירט .UncaughtExceptionHandler . מטפל זה הוא אחד לכל שרשור (כלומר, מטפל אחד בכל שרשור). במידת הצורך, תוכל ליצור מטפל משלך ולהגדיר אותו באמצעות שיטת setDefaultUncaughtExceptionHandler שנקראת על אובייקט Thread .

ריבוי השרשורים

ניתוח שאלות ותשובות מראיונות למפתח Java.  חלק 12 - 5

108. אילו כלים אתה מכיר לעבודה עם ריבוי הליכים?

כלים בסיסיים/בסיסיים לשימוש ב-Multithreading ב-Java:
  • סינכרון הוא מנגנון לסגירה (חסימה) של שיטה/חסימה כאשר חוט נכנס אליה, משרשורים אחרים.
  • נדיף הוא מנגנון להבטחת גישה עקבית למשתנה על ידי חוטים שונים, כלומר, עם נוכחות של משנה זה על משתנה, כל פעולות ההקצאה והקריאה חייבות להיות אטומיות. במילים אחרות, שרשורים לא יעתיקו את המשתנה הזה לזיכרון המקומי שלהם וישנו אותו, אלא ישנו את הערך המקורי שלו.
קרא עוד על נדיפים כאן .
  • Runnable הוא ממשק שניתן ליישם (בפרט, שיטת הריצה שלו) במחלקה מסוימת:
public class CustomRunnable implements Runnable {
   @Override
   public void run() {
       // некоторая логика
   }
}
ולאחר שיצרת אובייקט של מחלקה זו, אתה יכול להתחיל שרשור חדש על ידי הגדרת אובייקט זה בבנאי של אובייקט Thread החדש וקריאה לשיטת start() שלו :
Runnable runnable = new CustomRunnable();
new Thread(runnable).start();
שיטת start מפעילה את שיטת run() המיושם בשרשור נפרד.
  • שרשור הוא מחלקה שיורשת ממנה (תוך כדי עקיפת שיטת הריצה ):
public class CustomThread extends Thread {
   @Override
   public void run() {
       // некоторая логика
   }
}
ועל ידי יצירת אובייקט של מחלקה זו והשקתו באמצעות המתודה start() , נשיק בכך שרשור חדש:
new CustomThread().start();
  • Concurrency היא חבילה עם כלים לעבודה בסביבה מרובת חוטים.
זה מורכב מ:
  • אוספים במקביל - סט אוספים המתמחים לעבודה בסביבה מרובת חוטים.
  • תורים - תורים מיוחדים לסביבה מרובת הליכי הליכי (חסימה ולא חוסמת).
  • סינכרוניזרים הם כלי עזר מיוחדים לעבודה בסביבה מרובת הליכי הליכה.
  • מבצעים הם מנגנונים ליצירת בריכות חוטים.
  • מנעולים - מנגנוני סנכרון חוטים (גמישים יותר מאלו הסטנדרטיים - סנכרון, המתן, הודע, הודע לכל).
  • Atomics הם מחלקות אופטימליות לביצוע מרובה הליכי הפעלה; כל פעולה היא אטומית.
קרא עוד על החבילה במקביל כאן .

109. מדברים על סנכרון בין שרשורים. למה משמשות המתודות wait(), notify() - notifyAll() join()?

עד כמה שהבנתי את השאלה, סנכרון בין שרשורים הוא על ה-key modifier - synchronized . ניתן למקם את השינוי הזה ישירות ליד הבלוק:
synchronized (Main.class) {
   // некоторая логика
}
או ישירות בחתימת השיטה:
public synchronized void move() {
   // некоторая логика}
כפי שאמרתי קודם, סינכרון הוא מנגנון שמאפשר לסגור בלוק/שיטה משרשורים אחרים כאשר שרשור אחד כבר נכנס אליו. חשבו על בלוק/שיטה כחדר. נחל כלשהו, ​​שהגיע אליו, ייכנס אליו וינעל אותו, נחל אחר, שהגיע לחדר וראה שהוא סגור, ימתינו בקרבתו עד שהוא פנוי. לאחר שעשה את עסקיו, החוט הראשון עוזב את החדר ומשחרר את המפתח. ולא בכדי דיברתי כל הזמן על המפתח, כי הוא באמת קיים. זהו אובייקט מיוחד שיש לו מצב תפוס/חופשי. אובייקט זה מצורף לכל אובייקט ג'אווה, ולכן בעת ​​שימוש בבלוק מסונכרן עלינו לציין בסוגריים את האובייקט שעליו אנו רוצים לסגור את הדלת:
Cat cat = new Cat();
synchronized (cat) {
   // некоторая логика
}
אתה יכול גם להשתמש ב-class mutex, כפי שעשיתי בדוגמה הראשונה ( Main.class ). כשאנחנו משתמשים בסנכרון על שיטה, אנחנו לא מציינים את האובייקט שעליו אנחנו רוצים לסגור, נכון? במקרה זה, עבור שיטה לא סטטית, היא תיסגר על המוטקס של האובייקט הזה , כלומר האובייקט הנוכחי של המחלקה הזו. הסטטי ייסגר על המוטקס של המחלקה הנוכחית ( this.getClass(); ). אתה יכול לקרוא עוד על mutex כאן . ובכן, קרא על סינכרון כאן . Wait() היא שיטה שמשחררת את ה-mutex ומכניסה את השרשור הנוכחי למצב המתנה, כאילו מחוברת למוניטור הנוכחי (משהו כמו עוגן). בגלל זה, ניתן לקרוא לשיטה זו רק מבלוק או מתודה מסונכרנים (אחרת, מה היא צריכה לשחרר ולמה היא צריכה לצפות). כמו כן, שים לב שזו שיטה של ​​המחלקה Object . ליתר דיוק, לא אחד, אלא אפילו שלושה:
  • Wait() - מכניס את השרשור הנוכחי למצב המתנה עד שרשור אחר יקרא לשיטת notify() או notifyAll() עבור אובייקט זה (נדבר על השיטות הללו מאוחר יותר).

  • Wait (זמן קצוב ארוך) - מעביר את השרשור הנוכחי למצב המתנה עד שרשור אחר יקרא לשיטת notify() או notifyAll() באובייקט זה או שפג תוקף הזמן הקצוב שצוין .

  • המתן (פסק זמן ארוך, int nanos) - בדומה לקודם, רק ננו מאפשר לך לציין ננו-שניות (הגדרת זמן מדויקת יותר).

  • Notify() היא שיטה המאפשרת לך להעיר שרשור אקראי אחד של בלוק הסנכרון הנוכחי. שוב, ניתן לקרוא לו רק בבלוק או בשיטה מסונכרנת (הרי במקומות אחרים לא יהיה לו מי לבטל את ההקפאה).

  • NotifyAll() היא שיטה המעירה את כל השרשורים הממתינים בצג הנוכחי (גם בשימוש רק בבלוק או בשיטה מסונכרנים ).

110. איך לעצור את הזרימה?

הדבר הראשון שיש לומר הוא שכאשר שיטת run() מבוצעת במלואה , השרשור מושמד אוטומטית. אבל לפעמים אתה צריך להרוג אותו לפני המועד, לפני השיטה הזו. אז מה עלינו לעשות אז? אולי לאובייקט Thread צריכה להיות שיטת stop() ? לא משנה איך זה! שיטה זו נחשבת למיושנת ועלולה להוביל לקריסות מערכת. ניתוח שאלות ותשובות מראיונות למפתח Java.  חלק 12 - 6נו, מה אז? ישנן שתי דרכים לעשות זאת: הראשונה היא להשתמש בדגל הבוליאני הפנימי שלך. בואו נסתכל על דוגמה. יש לנו יישום משלנו של שרשור שאמור להציג ביטוי מסוים על המסך עד שהוא נעצר לחלוטין:
public class CustomThread extends Thread {
private boolean isActive;

   public CustomThread() {
       this.isActive = true;
   }

   @Override
   public void run() {
       {
           while (isActive) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }

   public void stopRunningThread() {
       isActive = false;
   }
}
בעת שימוש בשיטת stopRunning() , הדגל הפנימי הופך ל-false ושיטת ההפעלה מפסיקה לפעול. בוא נריץ את זה ב- main :
System.out.println("Начало выполнения программы");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// пока наш основной поток спит, вспомогательный  CustomThread работает и выводит в коноль своё сообщение
thread.stopRunningThread();
System.out.println("Конец выполнения программы");
כתוצאה מכך, נראה משהו כזה בקונסולה:
התחלת הפעלת תוכנית השרשור מבצע לוגיקה כלשהי... השרשור מבצע לוגיקה כלשהי... השרשור מבצע לוגיקה מסוימת... השרשור מבצע לוגיקה מסוימת... השרשור מבצע לוגיקה כלשהי... השרשור מבצע לוגיקה כלשהי... סוף הפעלת התוכנית השרשור נעצר!
זה אומר שהשרשור שלנו עבד, פלט מספר מסוים של הודעות לקונסולה והופסק בהצלחה. אני מציין שמספר הפלט של ההודעות ישתנה מהפעלה להרצה; לפעמים השרשור הנוסף אפילו לא פלט שום דבר. כפי ששמתי לב, זה תלוי בזמן השינה של השרשור הראשי, ככל שהוא ארוך יותר, הסיכוי שהשרשור הנוסף לא יוציא כלום. עם זמן שינה של 1ms, הודעות כמעט אף פעם לא יוצאות, אבל אם אתה מגדיר את זה ל-20ms, זה כמעט תמיד עובד. אולי, כשהזמן קצר, לחוט פשוט אין זמן להתחיל ולהתחיל בעבודתו, ומיד נעצר. הדרך השנייה היא להשתמש בשיטת interrupted() באובייקט Thread , שמחזירה את הערך של דגל הפסיקה הפנימי (דגל זה הוא false כברירת מחדל ) ובשיטת interrupt() האחרת שלו , שמגדירה את הדגל הזה ל-true (כאשר זה הדגל נכון שהשרשור צריך להפסיק את עבודתו). בואו נסתכל על דוגמה:
public class CustomThread extends Thread {

   @Override
   public void run() {
       {
           while (!Thread.interrupted()) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }
}
הפעל ב- main :
System.out.println("Начало выполнения программы");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Конец выполнения программы");
התוצאה של הביצוע תהיה זהה למקרה הראשון, אבל אני אוהב יותר את הגישה הזו: אנחנו כותבים פחות קוד ומשתמשים יותר בפונקציונליות מוכנה וסטנדרטית. ניתוח שאלות ותשובות מראיונות למפתח Java.  חלק 12 - 7שם נעצור היום.ניתוח שאלות ותשובות מראיונות למפתח Java.  חלק 12 - 8
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION