CodeGym/Blog Java/Random-FR/Top 50 des questions et réponses d'entretien d'embauche p...
John Squirrels
Niveau 41
San Francisco

Top 50 des questions et réponses d'entretien d'embauche pour Java Core. Partie 2

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

Multithreading

24. Comment créer un nouveau thread en Java ?

D'une manière ou d'une autre, un thread est créé à l'aide de la classe Thread. Mais il existe différentes manières de procéder…
  1. Héritez de java.lang.Thread .
  2. Implémentez l' interface java.lang.Runnable — le constructeur de la classe Thread prend un objet Runnable.
Parlons de chacun d'eux.

Hériter de la classe Thread

Dans ce cas, nous faisons hériter notre classe de java.lang.Thread . Il a une méthode run() , et c'est exactement ce dont nous avons besoin. Toute la vie et la logique du nouveau fil seront dans cette méthode. C'est un peu comme une méthode principale pour le nouveau thread. Après cela, il ne reste plus qu'à créer un objet de notre classe et à appeler la méthode start() . Cela créera un nouveau thread et commencera à exécuter sa logique. Nous allons jeter un coup d'oeil:
/**
* 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();
   }
}
La sortie de la console ressemblera à ceci :
Fil-1 Fil-0 Fil-2
Autrement dit, même ici, nous voyons que les threads ne sont pas exécutés dans l'ordre, mais plutôt comme la JVM juge bon de les exécuter :)

Implémenter l'interface Runnable

Si vous êtes contre l'héritage et/ou héritez déjà d'une autre classe, vous pouvez utiliser l' interface java.lang.Runnable . Ici, nous faisons en sorte que notre classe implémente cette interface en implémentant la méthode run() , comme dans l'exemple ci-dessus. Il ne reste plus qu'à créer des objets Thread . Il semblerait que plus de lignes de code soient pires. Mais nous savons à quel point l'héritage est pernicieux et qu'il vaut mieux l'éviter par tous les moyens ;) Jetez un œil :
/**
* 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();
   }
}
Et voici le résultat :
Fil-0 Fil-1 Fil-2

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

Top 50 des questions et réponses d'entretien d'embauche pour Java Core.  Partie 2 - 2Un processus et un thread sont différents des manières suivantes :
  1. Un programme en cours d'exécution est appelé un processus, mais un thread est une partie d'un processus.
  2. Les processus sont indépendants, mais les threads sont des éléments d'un processus.
  3. Les processus ont des espaces d'adressage différents en mémoire, mais les threads partagent un espace d'adressage commun.
  4. La commutation de contexte entre les threads est plus rapide que la commutation entre les processus.
  5. La communication inter-processus est plus lente et plus coûteuse que la communication inter-thread.
  6. Toute modification apportée à un processus parent n'affecte pas un processus enfant, mais les modifications apportées à un thread parent peuvent affecter un thread enfant.

26. Quels sont les avantages du multithreading ?

  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 permet d'effectuer des tâches plus rapidement, car les threads s'exécutent indépendamment ;
  3. Le multithreading permet une meilleure utilisation de la mémoire cache, car les threads peuvent accéder aux ressources de mémoire partagées ;
  4. Le multithreading réduit le nombre de serveurs requis, car un serveur peut exécuter plusieurs threads simultanément.

27. Quels sont les états du cycle de vie d'un thread ?

Top 50 des questions et réponses d'entretien d'embauche pour Java Core.  Partie 2 - 3
  1. Nouveau : dans cet état, l'objet Thread est créé à l'aide de l'opérateur new, mais un nouveau thread n'existe pas encore. Le thread ne démarre pas tant que nous n'appelons pas la méthode start() .
  2. Runnable : dans cet état, le thread est prêt à s'exécuter après le start() méthode est appelée. 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 à partir d'un état prêt et il s'exécute.
  4. En attente/Bloqué : dans cet état, un thread n'est pas en cours d'exécution, mais il est toujours actif ou attend qu'un autre thread se termine.
  5. Mort/Terminé : lorsqu'un thread quitte la méthode run() , il est dans un état mort ou terminé.

28. Est-il possible d'exécuter un thread deux fois ?

Non, nous ne pouvons pas redémarrer un thread, car après le démarrage et l'exécution d'un thread, il passe à l'état Dead. Si nous essayons de démarrer un thread deux fois, une exception java.lang.IllegalThreadStateException sera levée. Nous allons jeter un coup d'oeil:
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();
   }
}
Il y aura une exception dès que l'exécution arrivera au deuxième démarrage du même thread. Essayez-le vous-même ;) Il vaut mieux le voir une fois que d'en entendre parler cent fois.

29. Que se passe-t-il si vous appelez run() directement sans appeler start() ?

Oui, vous pouvez certainement appeler la méthode run() , mais un nouveau thread ne sera pas créé et la méthode ne s'exécutera pas sur un thread séparé. Dans ce cas, nous avons un objet ordinaire appelant une méthode ordinaire. Si nous parlons de la méthode start() , alors c'est une autre affaire. Lorsque cette méthode est appelée, la JVM démarre un nouveau thread. Ce fil, à son tour, appelle notre méthode ;) Vous n'y croyez pas ? Tiens, fais un essai :
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();
   }
}
Et la sortie de la console ressemblera à ceci :
0123401234
Comme vous pouvez le voir, aucun fil n'a été créé. Tout fonctionnait comme dans une classe ordinaire. Tout d'abord, la méthode du premier objet a été exécutée, puis la seconde.

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

Un thread démon est un thread qui exécute des tâches avec une priorité inférieure à celle d'un autre thread. En d'autres termes, son travail consiste à effectuer des tâches auxiliaires qui ne doivent être effectuées qu'en conjonction avec un autre thread (principal). Il existe de nombreux threads démons qui s'exécutent automatiquement, tels que la récupération de place, le finaliseur, etc.

Pourquoi Java termine-t-il un thread démon ?

Le seul but du thread démon est de fournir un support en arrière-plan au thread d'un utilisateur. Par conséquent, si le thread principal est terminé, la JVM termine automatiquement tous ses threads démons.

Méthodes de la classe Thread

La classe java.lang.Thread fournit deux méthodes pour travailler avec un thread démon :
  1. public void setDaemon(boolean status) — Cette méthode indique s'il s'agira d'un thread démon. La valeur par défaut est false . Cela signifie qu'aucun thread démon ne sera créé à moins que vous ne le disiez spécifiquement.
  2. public boolean isDaemon() — Cette méthode est essentiellement un getter pour la variable démon , que nous définissons à l'aide de la méthode précédente.
Exemple:
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();
   }
}
Sortie console :
démon? vrai démon ? faux démon ? faux thread démon thread utilisateur thread utilisateur
À partir de la sortie, nous voyons qu'à l'intérieur du thread lui-même, nous pouvons utiliser la méthode statique currentThread() pour savoir de quel thread il s'agit. Alternativement, si nous avons une référence à l'objet thread, nous pouvons également le découvrir directement à partir de celui-ci. Cela fournit le niveau de configurabilité nécessaire.

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

Non. Si vous essayez de le faire, vous obtiendrez une IllegalThreadStateException . Cela signifie que nous ne pouvons créer un thread démon qu'avant qu'il ne démarre. Exemple:
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);
   }
}
Sortie console :
Travail... Exception dans le thread "main" java.lang.IllegalThreadStateException à java.lang.Thread.setDaemon(Thread.java:1359) à SetDaemonAfterStartExample.main(SetDaemonAfterStartExample.java:14)

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

Un crochet d'arrêt est un thread implicitement appelé avant l'arrêt de la machine virtuelle Java (JVM). Ainsi, nous pouvons l'utiliser pour libérer une ressource ou enregistrer un état lorsque la machine virtuelle Java s'arrête normalement ou anormalement. Nous pouvons ajouter un crochet d'arrêt en utilisant la méthode suivante :
Runtime.getRuntime().addShutdownHook(new ShutdownHookThreadExample());
Comme indiqué dans l'exemple :
/**
* 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();
       }
   }
}
Sortie console :
Maintenant, le programme va s'endormir. Appuyez sur Ctrl+C pour y mettre fin. crochet d'arrêt exécuté

33. Qu'est-ce que la synchronisation ?

En Java, la synchronisation est la capacité de contrôler l'accès de plusieurs threads à n'importe quelle ressource partagée. Lorsque plusieurs threads essaient d'effectuer la même tâche simultanément, vous pouvez obtenir un résultat incorrect. Pour résoudre ce problème, Java utilise la synchronisation, qui permet à un seul thread de s'exécuter à la fois. La synchronisation peut être réalisée de trois manières :
  • Synchroniser une méthode
  • Synchroniser un bloc spécifique
  • Synchronisation statique

Synchroniser une méthode

Une 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 le verrou de l'objet et le libère lorsque le thread termine sa tâche. Pour que cela fonctionne, vous devez ajouter le mot clé synchronized . Nous pouvons voir comment cela fonctionne en regardant un exemple :
/**
* 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);
   }
}
Et la sortie de la console est celle-ci :
J'enfile-0 Écris une lettre Je n'enfile pas-1 N'écris pas de lettre

Bloc de synchronisation

Un bloc synchronisé peut être utilisé pour effectuer une synchronisation sur n'importe quelle ressource particulière dans une méthode. Disons que dans une grande méthode (oui, vous ne devriez pas les écrire, mais parfois cela arrive), vous n'avez besoin de synchroniser qu'une petite section pour une raison quelconque. Si vous mettez tout le code de la 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 ("object to be locked") {
   // The code that must be protected
}
Pour éviter de répéter l'exemple précédent, nous allons créer des threads à l'aide de classes anonymes, c'est-à-dire que nous allons immédiatement implémenter l'interface 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();
   }
}

}
Et la sortie de la console est celle-ci :
J'écris1 Écris une lettre Je n'écris pas2 N'écris pas de lettre

Synchronisation statique

Si vous synchronisez une méthode statique, le verrouillage se produira sur la classe, pas sur l'objet. Dans cet exemple, nous effectuons une synchronisation statique en appliquant le mot clé synchronized à une méthode statique :
/**
* 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();
   }
}
Et la sortie de la console est celle-ci :
Je n'écris pas2 N'écris pas de lettre I Writer1 Écris une lettre

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

Dans la programmation multithread, le mot clé volatile est utilisé pour la sécurité des threads. Lorsqu'une variable modifiable est modifiée, la modification est visible pour tous les autres threads, de sorte qu'une variable peut être utilisée par un thread à la fois. En utilisant le mot-clé volatile , vous pouvez garantir qu'une variable est thread-safe et stockée dans la mémoire partagée, et que les threads ne la stockeront pas dans leurs caches. À quoi cela ressemble-t-il ?
private volatile AtomicInteger count;
Nous ajoutons simplement volatile à la variable. Mais gardez à l'esprit que cela ne signifie pas une sécurité totale des threads... Après tout, les opérations sur la variable peuvent ne pas être atomiques. Cela dit, vous pouvez utiliser des classes atomiques qui effectuent des opérations de manière atomique, c'est-à-dire dans une seule instruction CPU. Il existe de nombreuses classes de ce type dans le package java.util.concurrent.atomic .

35. Qu'est-ce qu'une impasse ?

En Java, le blocage est quelque chose qui peut se produire dans le cadre du multithreading. Un blocage peut se produire lorsqu'un thread attend le verrou d'un objet acquis par un autre thread et que le second thread attend le verrou de l'objet acquis par le premier thread. Cela signifie que les deux threads s'attendent et que l'exécution de leur code ne peut pas continuer. Top 50 des questions et réponses d'entretien d'embauche pour Java Core.  Partie 2 - 4Considérons un exemple qui a une classe qui implémente Runnable. Son constructeur prend deux ressources. La méthode run() acquiert le verrou pour eux dans l'ordre. Si vous créez deux objets de cette classe et transmettez les ressources dans un ordre différent, vous pouvez facilement vous retrouver dans une impasse :
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);
           }
       }
   }
}
Sortie console :
Le premier thread a acquis la première ressource Le deuxième thread a acquis la deuxième ressource

36. Comment évitez-vous l'impasse ?

Parce que nous savons comment se produit l'impasse, nous pouvons tirer quelques conclusions...
  • Dans l'exemple ci-dessus, le blocage se produit en raison du fait que nous avons un verrouillage imbriqué. Autrement dit, nous avons un bloc synchronisé à l'intérieur d'un bloc synchronisé. Pour éviter cela, au lieu d'imbriquer, vous devez créer une nouvelle couche d'abstraction supérieure, déplacer la synchronisation vers le niveau supérieur et éliminer le verrouillage imbriqué.
  • Plus vous verrouillez, plus il y aura de chances qu'il y ait un blocage. Par conséquent, chaque fois que vous ajoutez un bloc synchronisé, vous devez vous demander si vous en avez vraiment besoin et si vous pouvez éviter d'en ajouter un nouveau.
  • Utilisation de Thread.join() . Vous pouvez également rencontrer un blocage pendant qu'un thread en attend un autre. Pour éviter ce problème, vous pouvez envisager de définir un délai d'attente pour la méthode join() .
  • Si nous avons un fil, alors il n'y aura pas de blocage ;)

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

Si les courses réelles impliquent des voitures, alors les courses en multithreading impliquent des threads. Mais pourquoi? :/ Deux threads sont en cours d'exécution et peuvent accéder au même objet. Et ils peuvent tenter de mettre à jour l'état de l'objet partagé en même temps. Tout est clair jusqu'à présent, n'est-ce pas ? Les threads sont exécutés soit littéralement en parallèle (si le processeur a plus d'un cœur), soit séquentiellement, le processeur allouant des tranches de temps entrelacées. Nous ne pouvons pas gérer ces processus. Cela signifie que lorsqu'un thread lit les données d'un objet, nous ne pouvons pas garantir qu'il aura le temps de modifier l'objet AVANT qu'un autre thread ne le fasse. De tels problèmes surviennent lorsque nous avons ces combos "vérifier et agir". Qu'est-ce que cela signifie? Supposons que nous ayons une instruction if dont le corps modifie la condition if elle-même, par exemple :
int z = 0;

// Check
if (z < 5) {
// Act
   z = z + 5;
}
Deux threads pourraient entrer simultanément dans ce bloc de code lorsque z est toujours égal à zéro, puis les deux threads pourraient changer sa valeur. En conséquence, nous n'obtiendrons pas la valeur attendue de 5. Au lieu de cela, nous obtiendrions 10. Comment éviter cela ? Vous devez acquérir un verrou avant de vérifier et d'agir, puis relâchez le verrou par la suite. Autrement dit, vous devez faire en sorte que le premier thread entre dans le bloc if , effectue toutes les actions, modifie z , et ensuite seulement donne au thread suivant la possibilité de faire de même. Mais le thread suivant n'entrera pas dans le bloc if , puisque z sera désormais égal à 5 :
// Acquire the lock for z
if (z < 5) {
   z = z + 5;
}
// Release z's lock
===================================================

Au lieu d'une conclusion

Je tiens à dire merci à tous ceux qui ont lu jusqu'à la fin. C'était un long chemin, mais vous avez enduré! Peut-être que tout n'est pas clair. C'est normal. Lorsque j'ai commencé à étudier Java, je ne pouvais pas comprendre ce qu'est une variable statique. Mais ce n'est pas grave. J'ai dormi dessus, j'ai lu quelques sources supplémentaires, puis la compréhension est venue. La préparation d'un entretien est plus une question académique que pratique. Par conséquent, avant chaque entretien, vous devez revoir et rafraîchir dans votre mémoire les choses que vous n'utilisez peut-être pas très souvent.

Et comme toujours, voici quelques liens utiles :

Merci à tous d'avoir lu. A bientôt :) Mon profil GitHubTop 50 des questions et réponses d'entretien d'embauche pour Java Core.  Partie 2 - 5
Commentaires
  • Populaires
  • Nouveau
  • Anciennes
Tu dois être connecté(e) pour laisser un commentaire
Cette page ne comporte pas encore de commentaires