JavaRush /Blog Java /Random-FR /Top 50 des questions et réponses d'entretien avec Java Co...
Roman Beekeeper
Niveau 35

Top 50 des questions et réponses d'entretien avec Java Core. Partie 3

Publié dans le groupe Random-FR
Top 50 des questions et réponses d'entretien avec Java Core. Partie 1 : Top 50 des questions et réponses d'entretien avec Java Core. Partie 2

Multithreading

37. Comment créer un nouveau thread (flux) en Java ?

D'une manière ou d'une autre, la création se produit grâce à l'utilisation de la classe Thread. Mais il peut y avoir des options ici...
  1. Nous héritons dejava.lang.Thread
  2. Nous implémentons une interface dont l'objet accepte une classe java.lang.RunnableconstructeurThread
Parlons de chacun d'eux.

Nous héritons de la classe Thread

Pour que cela fonctionne, dans notre classe, nous héritons de java.lang.Thread. Il contient de la méthamphétamine run(), ce qui est exactement ce dont nous avons besoin. Toute la vie et la logique du nouveau fil seront dans cette méthode. C'est une sorte de mainméthode pour un nouveau fil de discussion. Après cela, il ne reste plus qu'à créer un objet de notre classe et à exécuter la méthode start(), qui créera un nouveau thread et exécutera la logique qui y est écrite. Regardons:
/**
* Пример того, 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();
   }
}
La sortie sur la console ressemblera à ceci :

Thread-1
Thread-0
Thread-2
Autrement dit, même ici, nous voyons que les threads ne sont pas exécutés à tour de rôle, mais comme la JVM l'a décidé)

Implémentation de l'interface Runnable

Si vous êtes contre l'héritage et/ou héritez déjà d'une des autres classes, vous pouvez utiliser le java.lang.Runnable. Ici, dans notre classe, nous implémentons cette interface et implémentons la méthode run(), comme c'était le cas dans cet exemple. Il vous suffit de créer plus d'objets Thread. Il semblerait que plus de lignes soient pires. Mais nous savons à quel point l'héritage est néfaste et qu'il vaut mieux l'éviter par tous les moyens ;) Regardons :
/**
* Пример того, 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();
   }
}
Et le résultat de l'exécution :

Thread-0
Thread-1
Thread-2

38. Quelle est la différence entre un processus et un thread ?

Top 50 des questions et réponses d'entretien avec Java Core.  Partie 3 - 1Il existe les différences suivantes entre un processus et un thread :
  1. Un programme en exécution est appelé un processus, tandis qu'un Thread est un sous-ensemble d'un processus.
  2. Les processus sont indépendants, tandis que les threads sont un sous-ensemble d'un processus.
  3. Les processus ont un espace d'adressage différent en mémoire, tandis que les threads contiennent un espace d'adressage commun.
  4. Le changement de contexte est plus rapide entre les threads qu’entre les processus.
  5. La communication interprocessus est plus lente et plus coûteuse que la communication interthread.
  6. Toute modification apportée au processus parent n'affecte pas le processus enfant, tandis que les modifications apportées au thread parent peuvent affecter le thread enfant.

39. Quels sont les avantages du multithreading ?

Top 50 des questions et réponses d'entretien avec Java Core.  Partie 3 - 2
  1. Le multithreading permet à une application/un programme de toujours répondre aux entrées même s'il exécute déjà certaines tâches en arrière-plan ;
  2. Le multithreading vous permet d'effectuer des tâches plus rapidement car les threads s'exécutent indépendamment ;
  3. Le multithreading offre une meilleure utilisation du cache car les threads partagent des ressources de mémoire communes ;
  4. Le multithreading réduit la quantité de serveur requise car un serveur peut exécuter plusieurs threads simultanément.

40. Quels sont les états du cycle de vie d’un thread ?

Top 50 des questions et réponses d'entretien avec Java Core.  Partie 3 - 3
  1. Nouveau : dans cet état, un objet de classe Threadest créé à l'aide de l'opérateur new, mais le thread n'existe pas. Le fil de discussion ne démarre que lorsque nous appelons le start().
  2. Runnable : dans cet état, le thread est prêt à s'exécuter après avoir appelé la méthode commencer(). Cependant, il n'a pas encore été sélectionné par le planificateur de threads.
  3. En cours d'exécution : dans cet état, le planificateur de threads sélectionne un thread dans l'état prêt et il s'exécute.
  4. En attente/bloqué : dans cet état, le thread n'est pas en cours d'exécution mais est toujours actif ou attend la fin d'un autre thread.
  5. Mort/Terminé : lorsque la méthode se termine, run()le thread est dans un état terminé ou mort.

41. Est-il possible de démarrer un fil de discussion deux fois ?

Non, nous ne pouvons pas redémarrer le thread car une fois le thread démarré et exécuté, il passe à l'état Dead. Donc, si nous essayons d'exécuter le thread deux fois, il lancera une runtimeException " java.lang.IllegalThreadStateException ". Regardons:
class DoubleStartThreadExample extends Thread {

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

   /**
    * Запускаем тред дважды
    */
   public static void main(String[] args) {
       DoubleStartThreadExample doubleStartThreadExample = new DoubleStartThreadExample();
       doubleStartThreadExample.start();
       doubleStartThreadExample.start();
   }
}
Dès que le travail atteint le deuxième démarrage du même fil, il y aura alors une exception. Essayez-le vous-même ;) il vaut mieux voir une fois que d’entendre cent fois.

42. Que se passe-t-il si vous appelez directement la méthode run() sans appeler la méthode start() ?

Oui, run()bien sûr, vous pouvez appeler une méthode, mais cela ne créera pas de nouveau thread et ne l'exécutera pas en tant que thread séparé. Dans ce cas, c’est un objet simple qui appelle une méthode simple. Si nous parlons de méthode start(), c'est une autre affaire. En lançant cette méthode, runtimeelle en lance une nouvelle et elle exécute à son tour notre méthode ;) Si vous ne me croyez pas, essayez-la :
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();
   }
}
Et le résultat sur la console ressemblera à ceci :

0123401234
On voit qu'aucun fil n'a été créé. Tout s'est déroulé comme dans un cours normal. La première méthode de classe a fonctionné, puis la seconde.

43. Qu'est-ce qu'un thread démon ?

Top 50 des questions et réponses d'entretien avec Java Core.  Partie 3 - 4Le thread démon (ci-après dénommé thread démon) est un thread qui exécute des tâches en arrière-plan en relation avec un autre thread. Autrement dit, son travail consiste à effectuer des tâches auxiliaires qui doivent être effectuées uniquement en conjonction avec un autre thread (principal). Il existe de nombreux threads démons qui fonctionnent automatiquement, tels que Garbage Collector, finalizer, etc.

Pourquoi Java ferme-t-il le thread démon ?

Le seul objectif d'un thread démon est de fournir des services au thread utilisateur pour la tâche de support en arrière-plan. Par conséquent, si le thread principal est terminé, le runtime ferme automatiquement tous ses threads démon.

Méthodes pour travailler dans la classe Thread

La classe java.lang.Threadfournit deux méthodes pour travailler avec le démon de thread :
  1. public void setDaemon(boolean status)- indique qu'il s'agira d'un thread démon. La valeur par défaut est false, ce qui signifie que des threads non démons seront créés, sauf indication contraire.
  2. public boolean isDaemon()- il s'agit essentiellement d'un getter pour la variable daemonque nous avons définie en utilisant la méthode précédente.
Exemple:
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();
   }
}
Sortie de la console :

демон?.. true
демон?.. false
демон?.. false
daemon thread
user thread
user thread
À partir de la sortie, nous voyons qu'à l'intérieur du thread lui-même, en utilisant une currentThread()méthode statique, nous pouvons découvrir de quel thread il s'agit d'une part, d'autre part, si nous avons une référence à l'objet de ce thread, nous pouvons découvrir directement à partir de celui-ci. Cela donne la flexibilité nécessaire dans la configuration.

44. Est-il possible de faire d'un thread un démon après sa création ?

Non. Si vous faites cela, une exception sera levée IllegalThreadStateException. Par conséquent, nous ne pouvons créer un thread démon qu’avant son démarrage. Exemple:
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);
   }
}
Sortie de la console :

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

45. Qu'est-ce qu'un crochet d'arrêt ?

Shutdownhook est un thread qui est implicitement appelé avant l'arrêt de la JVM (Java Virtual Machine). Nous pouvons donc l'utiliser pour nettoyer une ressource ou sauvegarder l'état lorsque la machine virtuelle Java s'arrête normalement ou soudainement. Nous pouvons ajouter shutdown hooken utilisant la méthode suivante :
Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());
Comme le montre l'exemple :
/**
* Программа, которая показывает 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();
       }
   }
}
Sortie de la console :

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

46. ​​​​​​Qu'est-ce que la synchronisation ?

La synchronisation en Java est la possibilité de contrôler l'accès de plusieurs threads à n'importe quelle ressource partagée. Lorsque plusieurs threads tentent d'effectuer la même tâche, il existe une possibilité d'obtenir un résultat erroné. Pour surmonter ce problème, Java utilise la synchronisation, grâce à laquelle un seul thread peut fonctionner à la fois. La synchronisation peut être réalisée de trois manières :
  • Méthode de synchronisation
  • En synchronisant un bloc spécifique
  • Synchronisation statique

Synchronisation des méthodes

La méthode synchronisée est utilisée pour verrouiller un objet pour toute ressource partagée. Lorsqu'un thread appelle une méthode synchronisée, il acquiert automatiquement un verrou sur cet objet et le libère lorsque le thread termine sa tâche. Pour que cela fonctionne, vous devez ajouter le mot-clé synchronisé . Voyons comment cela fonctionne avec un exemple :
/**
* Пример, где мы синхронизируем метод. То есть добавляем ему слово 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);
   }
}
Et le résultat sur la console :

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

Bloc de synchronisation

Un bloc synchronisé peut être utilisé pour effectuer une synchronisation sur n’importe quelle ressource de méthode spécifique. Disons que dans une méthode à grande échelle (oui, oui, vous ne pouvez pas écrire de telles choses, mais cela arrive parfois), vous n'avez besoin de synchroniser qu'une petite partie, pour une raison quelconque. Si vous mettez tous les codes d’une méthode dans un bloc synchronisé, cela fonctionnera de la même manière qu’une méthode synchronisée. La syntaxe ressemble à ceci :
synchronized (“an object для блокировки”) {
   // сам code, который нужно защитить
}
Afin de ne pas répéter l'exemple précédent, nous allons créer des threads via des classes anonymes, c'est-à-dire implémenter immédiatement l'interface 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();
   }
}

}
et sortie vers la console

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

Synchronisation statique

Si vous synchronisez une méthode statique, le verrou sera sur la classe, pas sur l'objet. Dans cet exemple, nous appliquons le mot-clé synchronisé à une méthode statique pour effectuer une synchronisation statique :
/**
* Вот 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();
   }
}
et la sortie sur la console :

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

47. Qu'est-ce qu'une variable volatile ?

Le mot-clé volatileest utilisé dans la programmation multithread pour assurer la sécurité des threads, car une modification apportée à une variable mutable est visible par tous les autres threads, de sorte qu'une variable peut être utilisée par un thread à la fois. En utilisant le mot-clé, volatilevous pouvez garantir que la variable sera thread-safe et sera stockée dans la mémoire partagée, et que les threads ne la prendront pas dans leur cache. À quoi cela ressemble-t-il?
private volatile AtomicInteger count;
Nous ajoutons simplement à la variable volatile. Mais cela ne signifie pas une sécurité totale des threads... Après tout, les opérations ne peuvent pas être atomiques sur une variable. Mais vous pouvez utiliser Atomicdes classes qui effectuent l'opération de manière atomique, c'est-à-dire en une seule exécution par le processeur. De nombreuses classes de ce type peuvent être trouvées dans le package java.util.concurrent.atomic.

48. Qu'est-ce que l'impasse

Deadlock en Java fait partie du multithreading. Un blocage peut se produire dans une situation où un thread attend un verrou d'objet acquis par un autre thread et qu'un deuxième thread attend un verrou d'objet acquis par le premier thread. Ainsi, ces deux threads s’attendent et ne continueront pas à exécuter leur code. Top 50 des questions et réponses d'entretien avec Java Core.  Partie 3 à 5Regardons un exemple dans lequel il existe une classe qui implémente Runnable. Il accepte deux ressources dans son constructeur. Dans la méthode run(), elle prend le verrou pour eux un par un, donc si vous créez deux objets de cette classe et transférez les ressources dans des ordres différents, vous pouvez facilement tomber sur un verrou :
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);
           }
       }
   }
}
Sortie de la console :

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

49. Comment éviter une impasse ?

Sur la base de ce que nous savons comment se produit une impasse, nous pouvons tirer quelques conclusions...
  • Comme le montre l’exemple ci-dessus, le blocage était dû à l’imbrication des verrous. Autrement dit, à l'intérieur d'une serrure, il y en a une ou plusieurs autres. Cela peut être évité de la manière suivante : au lieu d'imbrication, vous devez ajouter une nouvelle abstraction par-dessus, attribuer le verrou à un niveau supérieur et supprimer les verrous imbriqués.
  • Plus il y a de blocage, plus il y a de chances qu’il y ait une impasse. Par conséquent, chaque fois que vous ajoutez un verrou, vous devez vous demander s’il est vraiment nécessaire et si l’ajout d’un nouveau peut être évité.
  • Les usages Thread.join(). Un blocage peut également être effectué lorsqu'un thread en attend un autre. Pour éviter ce problème, vous pouvez envisager de fixer une limite de temps à join()la méthode.
  • Si nous n'avons qu'un seul thread, il n'y aura pas de blocage ;)

50. Qu'est-ce qu'une condition de concurrence critique ?

Si dans les courses réelles les voitures fonctionnent, alors dans la terminologie de course du multi-threading, les threads fonctionnent dans les courses. Mais pourquoi? Deux threads sont en cours d'exécution et peuvent avoir accès au même objet. Et ils peuvent essayer de mettre à jour l’état en même temps. Jusqu’à présent, tout est clair, n’est-ce pas ? Ainsi, les threads fonctionnent soit en parallèle réel (s'il y a plus d'un cœur dans le processeur), soit en parallèle conditionnel, lorsque le processeur alloue une courte période de temps. Et nous ne pouvons pas contrôler ces processus, nous ne pouvons donc pas garantir que lorsqu'un thread lit les données d'un objet, il aura le temps de les modifier AVANT qu'un autre thread ne le fasse. Des problèmes comme celui-ci se produisent lorsque cette combinaison tester et agir est mise en œuvre. Qu'est-ce que ça veut dire? Par exemple, nous avons ifune expression dans le corps dont la condition elle-même change, c'est-à-dire :
int z = 0;

// проверь
if (z < 5) {
//действуй
   z = z + 5;
}
Il peut donc y avoir une situation où deux threads entrent simultanément dans ce bloc de code à un moment où z est toujours égal à zéro et changent ensemble cette valeur. Et au final nous n'obtiendrons pas la valeur attendue de 5, mais de 10. Comment éviter cela ? Vous devez verrouiller avant et après l'exécution. Autrement dit, pour que le premier thread entre dans le bloc if, effectuez toutes les actions, modifiez-le zet donnez ensuite au thread suivant la possibilité de le faire. Mais le prochain thread n'entrera pas dans le bloc if, puisqu'il zsera déjà égal à 5 ​​:
// получить блокировку для z
if (z < 5) {
   z = z + 5;
}
// выпустить из блокировки z
===================================================

Au lieu de la sortie

Je tiens à remercier tous ceux qui ont lu jusqu'à la fin. Ce fut un long voyage et vous l'avez réussi ! Tout n’est peut-être pas clair. C'est bon. Dès que j’ai commencé à apprendre Java, je n’arrivais pas à comprendre ce qu’était une variable statique. Mais rien, j'ai dormi avec cette pensée, j'ai lu quelques sources supplémentaires et j'ai enfin compris. Se préparer à un entretien est plus une question académique que pratique. Par conséquent, avant chaque entretien, vous devez répéter et rafraîchir votre mémoire sur des choses que vous n'utiliserez peut-être pas très souvent.

Et comme toujours, des liens utiles :

Merci à tous d'avoir lu, à bientôt) Mon profil sur GitHub
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION