JavaRush /בלוג Java /Random-HE /50 השאלות והתשובות המובילות לראיון ליבת Java. חלק 3
Roman Beekeeper
רָמָה

50 השאלות והתשובות המובילות לראיון ליבת Java. חלק 3

פורסם בקבוצה
50 השאלות והתשובות המובילות לראיון ליבת Java. חלק 1 מובילות 50 שאלות ותשובות בראיון ליבת Java. חלק 2

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

37. איך יוצרים שרשור (זרימה) חדש בג'אווה?

כך או אחרת, היצירה מתרחשת באמצעות השימוש במחלקה Thread. אבל אולי יש כאן אפשרויות...
  1. אנחנו יורשים מjava.lang.Thread
  2. אנו מיישמים ממשק java.lang.Runnableשהאובייקט שלו מקבל Threadמחלקת קונסטרוקטור
בואו נדבר על כל אחד מהם.

אנחנו יורשים מכיתה Thread

כדי לגרום לזה לעבוד, בכיתה שלנו אנחנו יורשים מ java.lang.Thread. הוא מכיל מת' run(), וזה בדיוק מה שאנחנו צריכים. כל החיים וההיגיון של החוט החדש יהיו בשיטה זו. זו סוג של mainשיטה לשרשור חדש. לאחר מכן, כל מה שנותר הוא ליצור אובייקט של המחלקה שלנו ולהפעיל את השיטה start(), שתיצור שרשור חדש ותפעיל את ההיגיון שנכתב בו. בואו נראה:
/**
* Пример того, How создавать треды путем наследования {@link Thread} класса.
*/
class ThreadInheritance extends Thread {

   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }

   public static void main(String[] args) {
       ThreadInheritance threadInheritance1 = new ThreadInheritance();
       ThreadInheritance threadInheritance2 = new ThreadInheritance();
       ThreadInheritance threadInheritance3 = new ThreadInheritance();
       threadInheritance1.start();
       threadInheritance2.start();
       threadInheritance3.start();
   }
}
פלט הקונסולה יהיה כך:

Thread-1
Thread-0
Thread-2
כלומר, גם כאן אנו רואים שהשרשורים מבוצעים לא בתורם, אלא כפי שה-JVM החליט)

הטמעת ממשק Runnable

אם אתה נגד ירושה ו/או כבר יורשת אחת מהמעמדות האחרים, אתה יכול להשתמש ב- java.lang.Runnable. כאן בכיתה שלנו אנו מיישמים את הממשק הזה ומיישמים את השיטה run(), כפי שהייתה בדוגמה ההיא. אתה רק צריך ליצור אובייקטים נוספים Thread. נראה שיותר קווים גרועים יותר. אבל אנחנו יודעים כמה תורשה מזיקה ושעדיף להימנע ממנה בכל האמצעים ;) בואו נסתכל:
/**
* Пример того, How создавать треды из интерфейса {@link Runnable}.
* Здесь проще простого - реализуем этот интерфейс и потом передаем в конструктор
* экземпляр реализуемого an object.
*/
class ThreadInheritance implements Runnable {

   @Override
   public void run() {
       System.out.println(Thread.currentThread().getName());
   }

   public static void main(String[] args) {
       ThreadInheritance runnable1 = new ThreadInheritance();
       ThreadInheritance runnable2 = new ThreadInheritance();
       ThreadInheritance runnable3 = new ThreadInheritance();

       Thread threadRunnable1 = new Thread(runnable1);
       Thread threadRunnable2 = new Thread(runnable2);
       Thread threadRunnable3 = new Thread(runnable3);

       threadRunnable1.start();
       threadRunnable2.start();
       threadRunnable3.start();
   }
}
ותוצאת הביצוע:

Thread-0
Thread-1
Thread-2

38. מה ההבדל בין תהליך לחוט?

50 השאלות והתשובות המובילות לראיון ליבת Java.  חלק 3 - 1ישנם ההבדלים הבאים בין תהליך לשרשור:
  1. תוכנית בביצוע נקראת תהליך, בעוד שרשור הוא תת-קבוצה של תהליך.
  2. תהליכים הם בלתי תלויים, ואילו חוטים הם תת-קבוצה של תהליך.
  3. לתהליכים יש מרחב כתובות שונה בזיכרון, בעוד שרשורים מכילים מרחב כתובות משותף.
  4. החלפת הקשר מהירה יותר בין שרשורים בהשוואה לתהליכים.
  5. תקשורת בין-תהליכית איטית ויקרה יותר מתקשורת בין חוטים.
  6. כל שינוי בתהליך האב אינו משפיע על תהליך הצאצא, בעוד ששינויים בשרשור האב יכולים להשפיע על השרשור הצאצא.

39. מהם היתרונות של ריבוי השחלות?

50 השאלות והתשובות המובילות לראיון ליבת Java.  חלק 3 - 2
  1. ריבוי השרשורים מאפשר ליישום/תוכנית להגיב תמיד לקלט גם אם הוא כבר מפעיל כמה משימות רקע;
  2. ריבוי ההליכים מאפשר לך להשלים משימות מהר יותר מכיוון שהשרשורים מופעלים באופן עצמאי;
  3. ריבוי השחלות מספק ניצול טוב יותר של מטמון מכיוון שהשרשורים חולקים משאבי זיכרון משותפים;
  4. ריבוי השרשורים מפחית את כמות השרתים הנדרשת מכיוון ששרת אחד יכול להריץ שרשורים מרובים בו זמנית.

40. מהם המצבים במחזור החיים של חוט?

50 השאלות והתשובות המובילות לראיון ליבת Java.  חלק 3 - 3
  1. חדש: במצב זה Threadנוצר אובייקט מחלקה באמצעות האופרטור החדש, אך השרשור אינו קיים. השרשור לא מתחיל עד שנקרא ל- start().
  2. ניתן להרצה: במצב זה, השרשור מוכן להפעלה לאחר קריאה לשיטה הַתחָלָה(). עם זאת, הוא עדיין לא נבחר על ידי מתזמן השרשור.
  3. פועל: במצב זה, מתזמן השרשור בוחר שרשור ממצב מוכן והוא פועל.
  4. ממתין/חסום: במצב זה השרשור אינו פועל אך עדיין חי או ממתין להשלמת שרשור אחר.
  5. Dead/Terminated: כאשר השיטה יוצאת, run()השרשור נמצא במצב סיום או מת.

41. האם אפשר לפתוח שרשור פעמיים?

לא, אנחנו לא יכולים להפעיל מחדש את השרשור כי ברגע שהשרשור מופעל ומבוצע, הוא עובר למצב מת. אז אם ננסה להריץ את השרשור פעמיים, הוא יזרוק את ה-runtimeException " java.lang.IllegalThreadStateException ". בואו נראה:
class DoubleStartThreadExample extends Thread {

   /**
    * Имитируем работу треда
    */
   public void run() {
	// что-то происходит. Для нас не существенно на этом этапе
   }

   /**
    * Запускаем тред дважды
    */
   public static void main(String[] args) {
       DoubleStartThreadExample doubleStartThreadExample = new DoubleStartThreadExample();
       doubleStartThreadExample.start();
       doubleStartThreadExample.start();
   }
}
ברגע שהעבודה תגיע להתחלה השנייה של אותו שרשור, אז יהיה חריג. נסה את זה בעצמך ;) עדיף לראות פעם אחת מאשר לשמוע מאה פעמים.

42. מה אם אתה קורא למתודה run() ישירות מבלי לקרוא למתודה start()?

כן, run()כמובן שאתה יכול לקרוא למתודה, אבל זה לא ייצור שרשור חדש ויבצע אותו כחוט נפרד. במקרה זה, מדובר באובייקט פשוט שקורא לשיטה פשוטה. אם אנחנו מדברים על השיטה start(), אז זה עניין אחר. על ידי השקת שיטה זו, runtimeהיא משיקה שיטה חדשה והיא, בתורה, מפעילה את השיטה שלנו ;) אם אתה לא מאמין לי, נסה את זה:
class ThreadCallRunExample extends Thread {

   public void run() {
       for (int i = 0; i < 5; i++) {
           System.out.print(i);
       }
   }

   public static void main(String args[]) {
       ThreadCallRunExample runExample1 = new ThreadCallRunExample();
       ThreadCallRunExample runExample2 = new ThreadCallRunExample();

       // просто будут вызваны в потоке main два метода, один за другим.
       runExample1.run();
       runExample2.run();
   }
}
והפלט לקונסולה יהיה כך:

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

43. מהו שרשור דמון?

50 השאלות והתשובות המובילות לראיון ליבת Java.  חלק 3 - 4שרשור Daemon (להלן שרשור דמון) הוא שרשור המבצע משימות ברקע ביחס לשרשור אחר. כלומר, תפקידו לבצע משימות עזר שצריך לעשות רק בשילוב עם חוט אחר (ראשי). ישנם שרשורי דמונים רבים שפועלים באופן אוטומטי, כגון אספן זבל, גימור וכו'.

מדוע Java סוגרת את שרשור הדמון?

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

שיטות עבודה בכיתת Thread

המחלקה java.lang.Threadמספקת שתי שיטות לעבודה עם דמון השרשור:
  1. public void setDaemon(boolean status)- מציין שזה יהיה שרשור דמון. ברירת המחדל היא false, מה שאומר שהשרשורים שאינם דמונים ייווצרו אלא אם צוין בנפרד.
  2. public boolean isDaemon()- בעצם זהו משבר עבור המשתנה daemonשקבענו בשיטה הקודמת.
דוגמא:
class DaemonThreadExample extends Thread {

   public void run() {
       // Проверяет, демон ли этот поток or нет
       if (Thread.currentThread().isDaemon()) {
           System.out.println("daemon thread");
       } else {
           System.out.println("user thread");
       }
   }

   public static void main(String[] args) {
       DaemonThreadExample thread1 = new DaemonThreadExample();
       DaemonThreadExample thread2 = new DaemonThreadExample();
       DaemonThreadExample thread3 = new DaemonThreadExample();

       // теперь thread1 - поток-демон.
       thread1.setDaemon(true);

       System.out.println("демон?.. " + thread1.isDaemon());
       System.out.println("демон?.. " + thread2.isDaemon());
       System.out.println("демон?.. " + thread3.isDaemon());

       thread1.start();
       thread2.start();
       thread3.start();
   }
}
פלט מסוף:

демон?.. true
демон?.. false
демон?.. false
daemon thread
user thread
user thread
מהפלט רואים שבתוך השרשור עצמו, בשיטה סטטית currentThread(), נוכל לברר באיזה חוט מדובר מצד אחד, מצד שני, אם יש לנו התייחסות למושא השרשור הזה, נוכל לברר. ישירות ממנו. זה נותן את הגמישות הדרושה בתצורה.

44. האם ניתן להפוך שרשור לדמון לאחר יצירתו?

לא. אם תעשה זאת זה יגרום לחריגה IllegalThreadStateException. לכן, אנחנו יכולים ליצור רק שרשור דמון לפני שהוא מתחיל. דוגמא:
class SetDaemonAfterStartExample extends Thread {

   public void run() {
       System.out.println("Working...");
   }

   public static void main(String[] args) {
       SetDaemonAfterStartExample afterStartExample = new SetDaemonAfterStartExample();
       afterStartExample.start();

       // здесь будет выброшено исключение
       afterStartExample.setDaemon(true);
   }
}
פלט מסוף:

Working...
Exception in thread "main" java.lang.IllegalThreadStateException
	at java.lang.Thread.setDaemon(Thread.java:1359)
	at SetDaemonAfterStartExample.main(SetDaemonAfterStartExample.java:14)

45. מהו כיבוי הוק?

Shutdownhook הוא שרשור שנקרא במרומז לפני שה-JVM (Java Virtual Machine) נכבה. אז אנחנו יכולים להשתמש בו כדי לנקות משאב או מצב שמירה כאשר ה-Java Virtual Machine נכבה כרגיל או פתאומי. אנו יכולים להוסיף shutdown hookבאמצעות השיטה הבאה:
Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());
כפי שמוצג בדוגמה:
/**
* Программа, которая показывает How запустить shutdown hook тред,
* который выполнится аккурат до окончания работы JVM
*/
class ShutdownHookThreadExample extends Thread {

   public void run() {
       System.out.println("shutdown hook задачу выполнил");
   }

   public static void main(String[] args) {

       Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());

       System.out.println("Теперь программа засыпает, нажмите ctrl+c чтоб завершить ее.");
       try {
           Thread.sleep(60000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
}
פלט מסוף:

Теперь программа засыпает, нажмите ctrl+c чтоб завершить ее.
shutdown hook задачу выполнил

46. ​​מהו סנכרון?

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

סנכרון שיטה

השיטה המסונכרנת משמשת לנעילת אובייקט עבור כל משאב משותף. כאשר שרשור קורא לשיטה מסונכרנת, הוא רוכש אוטומטית נעילה על אותו אובייקט ומשחרר אותו כאשר השרשור משלים את המשימה שלו. כדי לגרום לזה לעבוד, עליך להוסיף את מילת המפתח המסונכרנת . בוא נראה איך זה עובד עם דוגמה:
/**
* Пример, где мы синхронизируем метод. То есть добавляем ему слово synchronized.
* Есть два писателя, которые хотят использовать один принтер. Они подготовor свои поэмы
* И конечно же не хотят, чтоб их поэмы перемешались, а хотят, чтоб работа была сделана по * * * очереди для каждого из них
*/
class Printer {

   synchronized void print(List<String> wordsToPrint) {
       wordsToPrint.forEach(System.out::print);
       System.out.println();
   }

   public static void main(String args[]) {
       // один an object для двух тредов
       Printer printer  = new Printer();

       // создаем два треда
       Writer1 writer1 = new Writer1(printer);
       Writer2 writer2 = new Writer2(printer);

       // запускаем их
       writer1.start();
       writer2.start();
   }
}

/**
* Писатель номер 1, который пишет свою поэму.
*/
class Writer1 extends Thread {
   Printer printer;

   Writer1(Printer printer) {
       this.printer = printer;
   }

   public void run() {
       List<string> poem = Arrays.asList("Я ", this.getName(), " Пишу", " Письмо");
       printer.print(poem);
   }

}

/**
* Писатель номер 2, который пишет свою поэму.
*/
class Writer2 extends Thread {
   Printer printer;

   Writer2(Printer printer) {
       this.printer = printer;
   }

   public void run() {
       List<String> poem = Arrays.asList("Не Я ", this.getName(), " Не пишу", " Не Письмо");
       printer.print(poem);
   }
}
והפלט לקונסולה:

Я Thread-0 Пишу Письмо
Не Я Thread-1 Не пишу Не Письмо

בלוק סנכרון

ניתן להשתמש בבלוק מסונכרן לביצוע סנכרון על כל משאב שיטה ספציפי. נניח שבשיטה גדולה (כן, כן, אי אפשר לכתוב דברים כאלה, אבל לפעמים זה קורה) צריך לסנכרן רק חלק קטן, משום מה. אם תשים את כל הקודים של שיטה בבלוק מסונכרן, זה יעבוד כמו שיטה מסונכרנת. התחביר נראה כך:
synchronized (“an object для блокировки”) {
   // сам code, который нужно защитить
}
כדי לא לחזור על הדוגמה הקודמת, ניצור שרשורים דרך מחלקות אנונימיות – כלומר, מיישם מיד את ממשק Runnable.
/**
* Вот How добавляется блок синхронизации.
* Внутри нужно указать у кого будет взят мьютекс для блокировки.
*/
class Printer {

   void print(List<String> wordsToPrint) {
       synchronized (this) {
           wordsToPrint.forEach(System.out::print);
       }
       System.out.println();
   }

   public static void main(String args[]) {
       // один an object для двух тредов
       Printer printer = new Printer();

       // создаем два треда
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Я ", "Writer1", " Пишу", " Письмо");
               printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Не Я ", "Writer2", " Не пишу", " Не Письмо");
               printer.print(poem);
           }
       });

       // запускаем их
       writer1.start();
       writer2.start();
   }
}

}
ופלט לקונסולה

Я Writer1 Пишу Письмо
Не Я Writer2 Не пишу Не Письмо

סנכרון סטטי

אם תבצע סנכרון של שיטה סטטית, הנעילה תהיה על המחלקה, לא על האובייקט. בדוגמה זו, אנו מיישמים את מילת המפתח המסונכרנת על שיטה סטטית לביצוע סנכרון סטטי:
/**
* Вот How добавляется блок синхронизации.
* Внутри нужно указать у кого будет взят мьютекс для блокировки.
*/
class Printer {

   static synchronized void print(List<String> wordsToPrint) {
       wordsToPrint.forEach(System.out::print);
       System.out.println();
   }

   public static void main(String args[]) {

       // создаем два треда
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Я ", "Writer1", " Пишу", " Письмо");
               Printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("Не Я ", "Writer2", " Не пишу", " Не Письмо");
               Printer.print(poem);
           }
       });

       // запускаем их
       writer1.start();
       writer2.start();
   }
}
והפלט לקונסולה:

Не Я Writer2 Не пишу Не Письмо
Я Writer1 Пишу Письмо

47. מהו משתנה נדיף?

מילת המפתח volatileמשמשת בתכנות מרובה הליכי שרשור כדי לספק בטיחות שרשור מכיוון ששינוי למשתנה אחד שניתן לשינוי גלוי לכל שאר השרשורים, כך שניתן להשתמש במשתנה אחד על ידי שרשור אחד בכל פעם. באמצעות מילת המפתח, volatileאתה יכול להבטיח שהמשתנה יהיה בטוח בשרשור ויאוחסן בזיכרון משותף, והשרשורים לא יקחו אותו למטמון שלהם. כמו מה זה נראה?
private volatile AtomicInteger count;
אנחנו רק מוסיפים למשתנה volatile. אבל זה לא אומר בטיחות חוט מלאה... אחרי הכל, ייתכן שהפעולות לא יהיו אטומיות על משתנה. אבל אתה יכול להשתמש Atomicבמחלקות שמבצעות את הפעולה בצורה אטומית, כלומר בביצוע אחד על ידי המעבד. ניתן למצוא שיעורים רבים כאלה בחבילה java.util.concurrent.atomic.

48. מהו מבוי סתום

מבוי סתום בג'אווה הוא חלק מ-multithreading. מבוי סתום יכול להתרחש במצב שבו חוט ממתין על נעילת אובייקט שנרכשה על ידי חוט אחר, וחוט שני ממתין על נעילת אובייקט שנרכשה על ידי חוט ראשון. לפיכך, שני האשכולות הללו ממתינים זה לזה ולא ימשיכו להפעיל את הקוד שלהם. 50 השאלות והתשובות המובילות לראיון ליבת Java.  חלק 3 - 5בואו נסתכל על דוגמה שבה יש מחלקה המיישמת Runnable. הוא מקבל שני משאבים בקונסטרוקטור שלו. בתוך שיטת run() היא לוקחת את המנעול עבורם אחד אחד, כך שאם תיצור שני אובייקטים מהמחלקה הזו ותעביר את המשאבים בסדרים שונים, תוכל להיתקל בקלות במנעול:
class DeadLock {

   public static void main(String[] args) {
       final Integer r1 = 10;
       final Integer r2 = 15;

       DeadlockThread threadR1R2 = new DeadlockThread(r1, r2);
       DeadlockThread threadR2R1 = new DeadlockThread(r2, r1);

       new Thread(threadR1R2).start();
       new Thread(threadR2R1).start();
   }
}

/**
* Класс, который принимает два ресурса.
*/
class DeadlockThread implements Runnable {

   private final Integer r1;
   private final Integer r2;

   public DeadlockThread(Integer r1, Integer r2) {
       this.r1 = r1;
       this.r2 = r2;
   }

   @Override
   public void run() {
       synchronized (r1) {
           System.out.println(Thread.currentThread().getName() + " захватил ресурс: " + r1);

           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }

           synchronized (r2) {
               System.out.println(Thread.currentThread().getName() + " захватил ресурс: " + r2);
           }
       }
   }
}
פלט מסוף:

Первый тред захватил первый ресурс
Второй тред захватывает второй ресурс

49. איך למנוע מבוי סתום?

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

50. מהו תנאי גזע?

אם במרוצים אמיתיים המכוניות מבצעות, אז בטרמינולוגיית המירוצים של ריבוי הליכים, חוטים מופיעים במירוצים. אבל למה? ישנם שני שרשורים שפועלים ושיכולים להיות להם גישה לאותו אובייקט. והם יכולים לנסות לעדכן את המדינה באותו זמן. עד כאן הכל ברור, נכון? אז החוטים עובדים או במקביל אמיתי (אם יש יותר מליבה אחת במעבד) או מותנה במקביל, כאשר המעבד מקצה פרק זמן קצר. ואנחנו לא יכולים לשלוט בתהליכים האלה, אז אנחנו לא יכולים להבטיח שכאשר שרשור אחד קורא נתונים מאובייקט, יהיה לו זמן לשנות אותם לפני שרשור אחר יעשה זאת. בעיות כאלה קורות כאשר השילוב הזה של מבחן ומעשה עובר. מה זה אומר? לדוגמה, יש לנו ifביטוי בגוף שהמצב עצמו משתנה, כלומר:
int z = 0;

// проверь
if (z < 5) {
//действуй
   z = z + 5;
}
אז יכול להיות מצב שבו שני שרשורים נכנסים בו זמנית לגוש הקוד הזה בזמן שבו z עדיין שווה לאפס ויחד הם משנים את הערך הזה. ובסופו של דבר לא נקבל את הערך הצפוי של 5, אלא 10. איך להימנע מכך? אתה צריך לנעול לפני ואחרי ביצוע. כלומר, כדי שהשרשור הראשון ייכנס לבלוק if, בצע את כל הפעולות, שנה אותו zורק אז תן לשרשור הבא את ההזדמנות לעשות זאת. אבל השרשור הבא לא ייכנס לבלוק if, מכיוון zשהוא כבר יהיה שווה ל-5:
// получить блокировку для z
if (z < 5) {
   z = z + 5;
}
// выпустить из блокировки z
===================================================

במקום פלט

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

וכמו תמיד, קישורים שימושיים:

תודה לכולכם שקראתם, נתראה בקרוב) הפרופיל שלי ב-GitHub
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION