CodeGym/Java блог/Случаен/Топ 50 въпроса и отговори за интервю за работа за Java Co...
John Squirrels
Ниво
San Francisco

Топ 50 въпроса и отговори за интервю за работа за Java Core. Част 2

Публикувано в групата
Топ 50 въпроса и отговори за интервю за работа за Java Core. Част 1Топ 50 въпроса и отговори за интервю за работа за Java Core.  Част 2 - 1

Многопоточност

24. Как да създам нова нишка в Java?

По един or друг начин нишката се създава с помощта на класа Thread. Но има различни начини да направите това...
  1. Наследява java.lang.Thread .
  2. Внедрете интерфейса java.lang.Runnable — конструкторът на класа Thread приема Runnable обект.
Нека поговорим за всеки от тях.

Наследете класа Thread

В този случай ние караме нашия клас да наследява java.lang.Thread . Има метод run() и точно това ни трябва. Целият живот и логика на новата тема ще бъдат в този метод. Това е нещо като основен метод за новата нишка. След това остава само да създадем обект от нашия клас и да извикаме метода start() . Това ще създаде нова нишка и ще започне да изпълнява нейната логика. Нека да разгледаме:
/**
* An example of how to create threads by inheriting the {@link Thread} class.
*/
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();
   }
}
Изходът от конзолата ще бъде нещо подобно:
Нишка-1 Нишка-0 Нишка-2
Тоест дори тук виждаме, че нишките се изпълняват не по ред, а по-скоро Howто JVM сметне за добре да ги изпълни :)

Внедрете интерфейса Runnable

Ако сте против наследяването и/or вече наследявате някой друг клас, можете да използвате интерфейса java.lang.Runnable . Тук караме нашия клас да реализира този интерфейс чрез имплементиране на метода run() , точно Howто в примера по-горе. Остава само да създадете Thread обекти. Изглежда, че повече редове code са по-лоши. Но знаем колко пагубно е наследството и че е по-добре да го избягваме по всяHowъв начин ;) Вижте:
/**
* An example of how to create threads from the {@link Runnable} interface.
* It's easier than easy — we implement this interface and then pass an instance of our object
* to the constructor.
*/
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();
   }
}
И ето резултата:
Нишка-0 Нишка-1 Нишка-2

25. Каква е разликата между процес и нишка?

Топ 50 въпроса и отговори за интервю за работа за Java Core.  Част 2 - 2Процес и нишка се различават по следните начини:
  1. Работеща програма се нарича процес, но нишката е част от процес.
  2. Процесите са независими, но нишките са части от процес.
  3. Процесите имат различни addressни пространства в паметта, но нишките споделят общо addressно пространство.
  4. Превключването на контекст между нишки е по-бързо от превключването между процеси.
  5. Комуникацията между процесите е по-бавна и по-скъпа от комуникацията между нишки.
  6. Всички промени в родителски процес не засягат дъщерен процес, но промените в родителска нишка могат да засегнат дъщерна нишка.

26. Какви са предимствата на многопоточността?

  1. Многонишковостта позволява на приложението/програмата винаги да реагира на въвеждане, дори ако вече изпълнява някои фонови задачи;
  2. Многонишковостта прави възможно по-бързото изпълнение на задачите, тъй като нишките се изпълняват независимо;
  3. Многонишковостта осигурява по-добро използване на кеш паметта, тъй като нишките имат достъп до споделени ресурси на паметта;
  4. Многонишковостта намалява броя на необходимите сървъри, тъй като един сървър може да изпълнява няколко нишки едновременно.

27. Какви са състоянията в жизнения цикъл на една нишка?

Топ 50 въпроса и отговори за интервю за работа за Java Core.  Част 2 - 3
  1. Нов: В това състояние обектът Thread се създава с помощта на оператора new, но нова нишка все още не съществува. Нишката не стартира, докато не извикаме метода start() .
  2. Runnable: В това състояние нишката е готова за изпълнение след start() методът се нарича. Той обаче все още не е избран от програмата за планиране на нишки.
  3. Изпълнение: В това състояние планировчикът на нишки избира нишка от състояние на готовност и тя се изпълнява.
  4. Чакащ/блокиран: в това състояние една нишка не работи, но все още е жива or чака друга нишка да завърши.
  5. Мъртъв/Прекратен: когато нишка излезе от метода run() , тя е в мъртво or прекратено състояние.

28. Възможно ли е една нишка да се стартира два пъти?

Не, не можем да рестартираме нишка, защото след като нишката стартира и работи, тя преминава в състояние Dead. Ако се опитаме да стартираме нишка два пъти, ще бъде хвърлено изключение java.lang.IllegalThreadStateException . Нека да разгледаме:
class DoubleStartThreadExample extends Thread {

   /**
    * Simulate the work of a thread
    */
   public void run() {
	// Something happens. At this state, this is not essential.
   }

   /**
    * Start the thread twice
    */
   public static void main(String[] args) {
       DoubleStartThreadExample doubleStartThreadExample = new DoubleStartThreadExample();
       doubleStartThreadExample.start();
       doubleStartThreadExample.start();
   }
}
Ще има изключение веднага щом изпълнението стигне до второто стартиране на същата нишка. Опитайте сами ;) По-добре е да видите това веднъж, отколкото да чуете за него сто пъти.

29. Какво става, ако извикате run() директно, без да извиквате start()?

Да, със сигурност можете да извикате метода run() , но няма да бъде създадена нова нишка и методът няма да се изпълнява в отделна нишка. В този случай имаме обикновен обект, който извиква обикновен метод. Ако говорим за метода start() , това е друг въпрос. Когато този метод бъде извикан, JVM стартира нова нишка. Тази тема от своя страна извиква нашия метод ;) Не вярвате? Ето, опитайте:
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();

       // Two ordinary methods will be called in the main thread, one after the other.
       runExample1.run();
       runExample2.run();
   }
}
И изходът на конзолата ще изглежда така:
0123401234
Както можете да видите, не е създадена нишка. Всичко работи точно Howто в обикновен клас. Първо беше изпълнен методът на първия обект, а след това на втория.

30. Какво е демон нишка?

Демон нишка е нишка, която изпълнява задачи с по-нисък приоритет от друга нишка. С други думи, работата му е да изпълнява спомагателни задачи, които трябва да се извършват само във връзка с друга (основна) нишка. Има много демон нишки, които се изпълняват автоматично, като събиране на боклук, финализатор и т.н.

Защо Java прекратява нишка на демон?

Единствената цел на нишката на демон е да осигури фонова поддръжка на нишката на потребителя. Съответно, ако основната нишка бъде прекратена, тогава JVM автоматично прекратява всичките си демон нишки.

Методи на клас Thread

Класът java.lang.Thread предоставя два метода за работа с демон нишка:
  1. public void setDaemon(boolean status) — Този метод показва дали това ще бъде нишка на демон. По подразбиране е false . Това означава, че няма да бъдат създадени демон нишки, освен ако изрично не го кажете.
  2. public boolean isDaemon() — Този метод по същество е инструмент за получаване на променливата на daemon , която задаваме с помощта на предишния метод.
Пример:
class DaemonThreadExample extends Thread {

   public void run() {
       // Checks whether this thread is a daemon
       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();

       // Make thread1 a daemon thread.
       thread1.setDaemon(true);

       System.out.println("daemon? " + thread1.isDaemon());
       System.out.println("daemon? " + thread2.isDaemon());
       System.out.println("daemon? " + thread3.isDaemon());

       thread1.start();
       thread2.start();
       thread3.start();
   }
}
Конзолен изход:
демон? истински демон? фалшив демон? false daemon нишка потребителска нишка потребителска нишка
От изхода виждаме, че вътре в самата нишка можем да използваме статичния метод currentThread() , за да разберем коя е нишката. Като алтернатива, ако имаме препратка към обекта на нишката, можем също да разберем директно от него. Това осигурява необходимото ниво на конфигурируемост.

31. Възможно ли е да се направи нишка демон, след като е била създадена?

Не. Ако се опитате да направите това, ще получите IllegalThreadStateException . Това означава, че можем да създадем нишка на демон само преди да започне. Пример:
class SetDaemonAfterStartExample extends Thread {

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

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

       // An exception will be thrown here
       afterStartExample.setDaemon(true);
   }
}
Конзолен изход:
Работи... Изключение в нишката "main" java.lang.IllegalThreadStateException в java.lang.Thread.setDaemon(Thread.java:1359) в SetDaemonAfterStartExample.main(SetDaemonAfterStartExample.java:14)

32. Какво е кука за изключване?

Куката за изключване е нишка, която се извиква имплицитно преди Java виртуалната машина (JVM) да бъде изключена. По този начин можем да го използваме, за да освободим ресурс or да запазим състояние, когато виртуалната машина на Java се изключи нормално or необичайно. Можем да добавим кука за изключване, като използваме следния метод:
Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());
Както е показано в примера:
/**
* A program that shows how to start a shutdown hook thread,
* which will be executed right before the JVM shuts down
*/
class ShutdownHookThreadExample extends Thread {

   public void run() {
       System.out.println("shutdown hook executed");
   }

   public static void main(String[] args) {

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

       System.out.println("Now the program is going to fall asleep. Press Ctrl+C to terminate it.");
       try {
           Thread.sleep(60000);
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
}
Конзолен изход:
Сега програмата ще заспи. Натиснете Ctrl+C, за да го прекратите. куката за изключване е изпълнена

33. Какво е синхронизация?

В Java синхронизацията е способността да се контролира достъпът на множество нишки до всеки споделен ресурс. Когато няколко нишки се опитват да изпълнят една и съща задача едновременно, можете да получите неправилен резултат. За да реши този проблем, Java използва синхронизация, която позволява само една нишка да се изпълнява в даден момент. Синхронизирането може да се постигне по три начина:
  • Синхронизиране на метод
  • Синхронизиране на определен блок
  • Статична синхронизация

Синхронизиране на метод

Използва се синхронизиран метод за заключване на обект за всеки споделен ресурс. Когато нишка извика синхронизиран метод, тя автоматично получава заключването на обекта и го освобождава, когато нишката изпълни задачата си. За да работи това, трябва да добавите ключовата дума synchronized . Можем да видим How работи това, като разгледаме пример:
/**
* An example where we synchronize a method. That is, we add the synchronized keyword to it.
* There are two authors who want to use one printer. Each of them has composed their own poems
* And of course they don’t want their poems mixed up. Instead, they want work to be performed in * * * order for each of them
*/
class Printer {

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

   public static void main(String args[]) {
       // One object for two threads
       Printer printer  = new Printer();

       // Create two threads
       Writer1 writer1 = new Writer1(printer);
       Writer2 writer2 = new Writer2(printer);

       // Start them
       writer1.start();
       writer2.start();
   }
}

/**
* Author No. 1, who writes an original poem.
*/
class Writer1 extends Thread {
   Printer printer;

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

   public void run() {
       List<string> poem = Arrays.asList("I ", this.getName(), " Write", " A Letter");
       printer.print(poem);
   }

}

/**
* Author No. 2, who writes an original poem.
*/
class Writer2 extends Thread {
   Printer printer;

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

   public void run() {
       List<String> poem = Arrays.asList("I Do Not ", this.getName(), " Not Write", " No Letter");
       printer.print(poem);
   }
}
И изходът от конзолата е следният:
Аз Нишка-0 Пиша Писмо Аз Не Нишка-1 Не Пиша Не Писмо

Блок за синхронизация

Синхронизиран блок може да се използва за извършване на синхронизация на всеки конкретен ресурс в метод. Да кажем, че в голям метод (да, не трябва да ги пишете, но понякога се случват) трябва да синхронизирате само малък раздел по няHowва причина. Ако поставите целия code на метода в синхронизиран блок, той ще работи по същия начин като синхронизиран метод. Синтаксисът изглежда така:
synchronized ("object to be locked") {
   // The code that must be protected
}
За да избегнем повтарянето на предишния пример, ще създадем нишки, използвайки анонимни класове, т.е. веднага ще имплементираме интерфейса Runnable.
/**
* This is how a synchronization block is added.
* Inside the block, you need to specify which object's mutex will be acquired.
*/
class Printer {

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

   public static void main(String args[]) {
       // One object for two threads
       Printer printer = new Printer();

       // Create two threads
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("I ", "Writer1", " Write", " A Letter");
               printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("I Do Not ", "Writer2", " Not Write", " No Letter");
               printer.print(poem);
           }
       });

       // Start them
       writer1.start();
       writer2.start();
   }
}

}
И изходът от конзолата е следният:
Аз Писател1 Пиша писмо Аз не Писател2 Не пиша писмо

Статична синхронизация

Ако направите статичен метод синхронизиран, тогава заключването ще се случи върху класа, а не върху обекта. В този пример извършваме статична синхронизация чрез прилагане на ключовата дума synchronized към статичен метод:
/**
* This is how a synchronization block is added.
* Inside the block, you need to specify which object's mutex will be acquired.
*/
class Printer {

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

   public static void main(String args[]) {

       // Create two threads
       Thread writer1 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("I ", "Writer1", " Write", " A Letter");
               Printer.print(poem);
           }
       });
       Thread writer2 = new Thread(new Runnable() {
           @Override
           public void run() {
               List<String> poem = Arrays.asList("I Do Not ", "Writer2", " Not Write", " No Letter");
               Printer.print(poem);
           }
       });

       // Start them
       writer1.start();
       writer2.start();
   }
}
И изходът от конзолата е следният:
Аз не съм Писател2 Не пиша Писмо Аз Писател1 Пиша Писмо

34. Какво е променлива променлива?

В многопоточното програмиране ключовата дума volatile се използва за безопасност на нишката. Когато променлива променлива е модифицирана, промяната е видима за всички останали нишки, така че дадена променлива може да се използва от една нишка наведнъж. Като използвате ключовата дума volatile , можете да гарантирате, че дадена променлива е безопасна за нишки и се съхранява в споделена памет и че нишките няма да я съхраняват в своите кешове. на Howво прorча това
private volatile AtomicInteger count;
Просто добавяме volatile към променливата. Но имайте предвид, че това не означава пълна безопасност на нишката... В края на краищата операциите върху променливата може да не са атомарни. Въпреки това можете да използвате атомни класове, които извършват операции атомарно, т.е. в една инструкция на процесора. Има много такива класове в пакета java.util.concurrent.atomic .

35. Какво е безизходица?

В Java блокирането е нещо, което може да се случи като част от многопоточността. Блокировка може да възникне, когато една нишка чака за заключване на обект, получено от друга нишка, а втората нишка чака за заключване на обект, придобита от първата нишка. Това означава, че двете нишки чакат една друга и изпълнението на техния code не може да продължи. Топ 50 въпроса и отговори за интервю за работа за Java Core.  Част 2 - 4Нека разгледаме пример, който има клас, който имплементира 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();
   }
}

/**
* A class that accepts two resources.
*/
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() + " acquired resource: " + r1);

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

           synchronized (r2) {
               System.out.println(Thread.currentThread().getName() + " acquired resource: " + r2);
           }
       }
   }
}
Конзолен изход:
Първата нишка придоби първия ресурс Втората нишка придоби втория ресурс

36. Как избягвате задънената улица?

Тъй като знаем How възниква безизходица, можем да направим някои заключения...
  • В примера по-горе блокировката възниква поради факта, че имаме вложено заключване. Тоест имаме синхронизиран блок вътре в синхронизиран блок. За да избегнете това, instead of влагане, трябва да създадете нов по-висок слой на абстракция, да преместите синхронизацията на по-високо ниво и да премахнете вложеното заключване.
  • Колкото повече заключване правите, толкова по-вероятно е да има блокиране. Следователно всеки път, когато добавяте синхронизиран блок, трябва да помислите дали наистина имате нужда от него и дали можете да избегнете добавянето на нов.
  • Използване на Thread.join() . Можете също така да изпаднете в безизходица, докато една нишка чака друга. За да избегнете този проблем, можете да обмислите задаване на таймаут за метода join() .
  • Ако имаме една нишка, тогава няма да има задънена улица ;)

37. Какво е condition за състезание?

Ако състезанията в реалния живот включват автомобor, тогава състезанията в многопоточност включват нишки. Но защо? :/ Има две нишки, които се изпълняват и имат достъп до един и същи обект. И те могат да се опитат да актуализират състоянието на споделения обект едновременно. Дотук всичко е ясно, нали? Нишките се изпълняват or буквално паралелно (ако процесорът има повече от едно ядро), or последователно, като процесорът разпределя преплитащи се времеви отрязъци. Ние не можем да управляваме тези процеси. Това означава, че когато една нишка чете данни от обект, не можем да гарантираме, че тя ще има време да промени обекта ПРЕДИ друга нишка да направи това. Такива проблеми възникват, когато имаме тези комбинации "проверете и действайте". Какво означава това? Да предположим, че имаме оператор if , чието тяло променя самото condition if, например:
int z = 0;

// Check
if (z < 5) {
// Act
   z = z + 5;
}
Две нишки могат едновременно да влязат в този codeов блок, когато z все още е нула и тогава и двете нишки могат да променят стойността си. В резултат на това няма да получим очакваната стойност от 5. Вместо това ще получим 10. Как се избягва това? Трябва да получите заключване, преди да проверите и да действате, и след това да освободите заключването след това. Тоест, трябва да накарате първата нишка да влезе в блока if , да извърши всички действия, да промени z и едва след това да даде възможност на следващата нишка да направи същото. Но следващата нишка няма да влезе в блока if , тъй като z сега ще бъде 5:
// Acquire the lock for z
if (z < 5) {
   z = z + 5;
}
// Release z's lock
===================================================

Вместо заключение

Искам да благодаря на всички, които прочетоха до края. Беше дълъг път, но ти издържа! Може би не всичко е ясно. Това е нормално. Когато за първи път започнах да изучавам Java, не можех да разбера Howво е статична променлива. Но нищо страшно. Преспах го, прочетох още няколко източника и тогава дойде разбирането. Подготовката за интервю е по-скоро академичен, отколкото практически въпрос. В резултат на това преди всяко интервю трябва да прегледате и опресните в паметта си онези неща, които може да не използвате много често.

И Howто винаги, ето няколко полезни връзки:

Благодаря на всички за четенето. До скоро :) Моят профил в GitHubТоп 50 въпроса и отговори за интервю за работа за Java Core.  Част 2 - 5
Коментари
  • Популярен
  • Нов
  • Стар
Трябва да сте влезли, за да оставите коментар
Тази страница все още няма коментари