CodeGym /Java Blog /ランダム /Java Core に関する就職面接の質問と回答トップ 50。パート2
John Squirrels
レベル 41
San Francisco

Java Core に関する就職面接の質問と回答トップ 50。パート2

ランダム グループに公開済み
Java Core に関する就職面接の質問と回答トップ 50。パート1Java Core に関する就職面接の質問と回答トップ 50。 パート 2 - 1

マルチスレッド化

24. Java で新しいスレッドを作成するにはどうすればよいですか?

いずれにせよ、スレッドは Thread クラスを使用して作成されます。しかし、これにはさまざまな方法があります...
  1. java.lang.Threadを継承します。
  2. java.lang.Runnableインターフェイスを実装します。Threadクラスのコンストラクターは Runnable オブジェクトを受け取ります。
それぞれについて話しましょう。

Threadクラスを継承する

この場合、クラスにjava.lang.Threadを継承させます。run()メソッドがあり、それがまさに必要なものです。新しいスレッドのすべての機能とロジックはこのメソッド内にあります。これは、新しいスレッドのmainメソッドのようなものです。その後は、クラスのオブジェクトを作成し、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
つまり、ここでもスレッドが順番に実行されるのではなく、JVM がスレッドの実行に適していると判断したとおりに実行されることがわかります:)

Runnableインターフェイスを実装する

継承に反対する場合、またはすでに他のクラスを継承している場合は、java.lang.Runnableインターフェースを使用できます。ここでは、上の例と同様に、run()メソッドを実装することで、クラスにこのインターフェイスを実装させます。残っているのは、 Threadオブジェクトを作成することだけです。コードの行数が増えると、より悪くなるように思えます。しかし、私たちは継承がいかに有害であるかを知っており、それを絶対に避けたほうが良いことを知っています ;) 見てください:

/**
* 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. プロセスとスレッドの違いは何ですか?

Java Core に関する就職面接の質問と回答トップ 50。 パート 2 - 2プロセスとスレッドは次の点で異なります。
  1. 実行中のプログラムはプロセスと呼ばれますが、スレッドはプロセスの一部です。
  2. プロセスは独立していますが、スレッドはプロセスの一部です。
  3. プロセスはメモリ内に異なるアドレス空間を持ちますが、スレッドは共通のアドレス空間を共有します。
  4. スレッド間のコンテキストの切り替えは、プロセス間の切り替えよりも高速です。
  5. プロセス間通信は、スレッド間通信よりも遅く、コストがかかります。
  6. 親プロセスの変更は子プロセスには影響しませんが、親スレッドの変更は子スレッドに影響を与える可能性があります。

26. マルチスレッドの利点は何ですか?

  1. マルチスレッドにより、アプリケーション/プログラムは、既にバックグラウンド タスクを実行している場合でも、常に入力に応答できます。
  2. マルチスレッドを使用すると、スレッドが独立して実行されるため、タスクをより速く完了できます。
  3. マルチスレッドを使用すると、スレッドが共有メモリ リソースにアクセスできるため、キャッシュ メモリをより有効に利用できます。
  4. マルチスレッドでは、1 台のサーバーで複数のスレッドを同時に実行できるため、必要なサーバーの数が減ります。

27. スレッドのライフサイクルの状態は何ですか?

Java Core に関する就職面接の質問と回答トップ 50。 パート 2 ~ 3
  1. New:この状態では、new 演算子を使用してThreadオブジェクトが作成されますが、新しいスレッドはまだ存在しません。start()メソッドを呼び出すまで、スレッドは開始されません。
  2. 実行可能:この状態では、スレッドは start() の後に実行する準備ができています。 メソッドが呼び出されます。ただし、スレッド スケジューラによってまだ選択されていません。
  3. 実行中:この状態では、スレッド スケジューラが準備完了状態からスレッドを選択し、実行します。
  4. 待機中/ブロック済み:この状態では、スレッドは実行されていませんが、まだ生きているか、別のスレッドが完了するのを待っています。
  5. デッド/終了:スレッドがrun()メソッドを終了すると、スレッドはデッドまたは終了状態になります。

28. スレッドを 2 回実行することは可能ですか?

いいえ、スレッドを再起動することはできません。スレッドは開始して実行されると Dead 状態になるからです。スレッドを 2 回開始しようとすると、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();
   }
}
同じスレッドの 2 回目の実行が開始されるとすぐに例外が発生します。自分で試してみてください ;) これについて何百回も聞くよりも、一度見たほうが良いでしょう。

29. start() を呼び出さずに run() を直接呼び出した場合はどうなりますか?

はい、確かに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
ご覧のとおり、スレッドは作成されていません。すべては通常の授業と同じように行われました。まず、最初のオブジェクトのメソッドが実行され、次に 2 番目のオブジェクトが実行されました。

30. デーモンスレッドとは何ですか?

デーモンスレッドは、他のスレッドよりも低い優先順位でタスクを実行するスレッドです。言い換えれば、その仕事は、別の (メイン) スレッドと組み合わせてのみ実行する必要がある補助タスクを実行することです。ガベージ コレクション、ファイナライザーなど、自動的に実行されるデーモン スレッドが多数あります。

Java がデーモン スレッドを終了するのはなぜですか?

デーモン スレッドの唯一の目的は、ユーザーのスレッドにバックグラウンド サポートを提供することです。したがって、メインスレッドが終了すると、JVM はすべてのデーモンスレッドを自動的に終了します。

Thread クラスのメソッド

java.lang.Threadクラスは、デーモン スレッドを操作するための 2 つのメソッドを提供します
  1. public void setDaemon(boolean status) — このメソッドは、これがデーモン スレッドになるかどうかを示します。デフォルトはfalseです。これは、特に指定しない限り、デーモン スレッドは作成されないことを意味します。
  2. public boolean isDaemon() — このメソッドは基本的に、前のメソッドを使用して設定したデーモン変数のゲッターです。
例:

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 デーモンスレッド ユーザースレッド ユーザースレッド
出力から、スレッド自体の内部で静的な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);
   }
}
コンソール出力:
動作しています...スレッド「メイン」の例外 java.lang.IllegalThreadStateException at java.lang.Thread.setDaemon(Thread.java:1359) at SetDaemonAfterStartExample.main(SetDaemonAfterStartExample.java:14)

32. シャットダウンフックとは何ですか?

シャットダウンフックは、Java 仮想マシン (JVM) がシャットダウンされる前に暗黙的に呼び出されるスレッドです。したがって、これを使用して、Java 仮想マシンが正常または異常にシャットダウンしたときに、リソースを解放したり、状態を保存したりできます。次の方法を使用して シャットダウン フックを追加できます。

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 は一度に 1 つのスレッドのみを実行できる同期を使用します。同期は次の 3 つの方法で実現できます。
  • メソッドの同期
  • 特定のブロックを同期する
  • 静的同期

メソッドの同期

同期メソッドは、共有リソースのオブジェクトをロックするために使用されます。スレッドが同期メソッドを呼び出すと、オブジェクトのロックが自動的に取得され、スレッドがタスクを完了するとロックが解放されます。これを機能させるには、 synchronizedキーワードを追加する必要があります。例を見ると、これがどのように機能するかがわかります。

/**
* 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);
   }
}
そして、コンソール出力は次のようになります。
I Thread-0 手紙を書きます I Do Not Thread-1 手紙を書きません

同期ブロック

同期ブロックは、メソッド内の特定のリソースで同期を実行するために使用できます。大規模なメソッド (はい、作成すべきではありませんが、場合によっては作成することもあります) で、何らかの理由で小さなセクションのみを同期する必要があるとします。メソッドのコードをすべて synchronized ブロックに配置すると、synchronized メソッドと同じように動作します。構文は次のようになります。

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キーワードが使用されます。可変変数が変更されると、その変更は他のすべてのスレッドに表示されるため、変数は一度に 1 つのスレッドで使用できます。volatileキーワードを使用すると、変数がスレッドセーフで共有メモリに保存され、スレッドがその変数をキャッシュに保存しないことを保証できます。これはどのように見えますか?

private volatile AtomicInteger count;
変数にvolatile を 追加するだけです。ただし、これは完全なスレッドの安全性を意味するものではないことに注意してください。結局のところ、変数の操作はアトミックではない可能性があります。つまり、操作をアトミックに、つまり単一の CPU 命令で実行するAtomicクラスを使用できます。java.util.concurrent.atomicパッケージには、そのようなクラスが多数あります。

35. デッドロックとは何ですか?

Java では、デッドロックはマルチスレッドの一部として発生する可能性があります。デッドロックは、スレッドが別のスレッドによって取得されるオブジェクトのロックを待機し、2 番目のスレッドが最初のスレッドによって取得されるオブジェクトのロックを待機しているときに発生する可能性があります。これは、2 つのスレッドが互いに待機しており、コードの実行を続行できないことを意味します。Java Core に関する就職面接の質問と回答トップ 50。 パート 2 ~ 4Runnable を実装するクラスがある例を考えてみましょう。そのコンストラクターは 2 つのリソースを必要とします。run() メソッドはそれらのロックを順番に取得します。このクラスの 2 つのオブジェクトを作成し、リソースを異なる順序で渡すと、簡単にデッドロックが発生する可能性があります。

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);
           }
       }
   }
}
コンソール出力:
最初のスレッドが最初のリソースを取得しました 2 番目のスレッドが 2 番目のリソースを取得しました

36. デッドロックを回避するにはどうすればよいですか?

デッドロックがどのように発生するかを知っているので、いくつかの結論を導き出すことができます...
  • 上の例では、ロックがネストされているためにデッドロックが発生します。つまり、同期ブロックの中に同期ブロックがあります。これを回避するには、ネストする代わりに、新しいより高い抽象化レイヤーを作成し、同期をより高いレベルに移動し、ネストされたロックを削除する必要があります。
  • ロックを行うほど、デッドロックが発生する可能性が高くなります。したがって、同期ブロックを追加するたびに、それが本当に必要かどうか、新しいブロックの追加を回避できるかどうかを考える必要があります。
  • Thread.join()を使用します。あるスレッドが別のスレッドを待機している間にデッドロックが発生することもあります。この問題を回避するには、 join()メソッドにタイムアウトを設定することを検討してください。
  • スレッドが 1 つであれば、デッドロックは発生しません ;)

37. 競合状態とは何ですか?

実際のレースに車が関与する場合、マルチスレッドのレースにはスレッドが関与します。しかし、なぜ?:/ 2 つのスレッドが実行されており、同じオブジェクトにアクセスできます。また、同時に共有オブジェクトの状態を更新しようとする場合もあります。これまでのところすべてが明らかですよね?スレッドは文字通り並列 (プロセッサに複数のコアがある場合) またはプロセッサがインターリーブされたタイム スライスを割り当てて順次に実行されます。私たちはこれらのプロセスを管理することはできません。これは、あるスレッドがオブジェクトからデータを読み取るときに、他のスレッドが変更する前にオブジェクトを変更する時間が保証できないことを意味します。このような問題は、これらの「チェックアンドアクション」コンボを使用する場合に発生します。どういう意味ですか?たとえば、本体が if 条件自体を変更する ifステートメントがあるとします。

int z = 0;

// Check
if (z < 5) {
// Act
   z = z + 5;
}
z がまだ 0 の場合、2 つのスレッドが同時にこのコード ブロックに入り、両方のスレッドがその値を変更する可能性があります。結果として、期待値 5 は得られません。代わりに 10 が得られます。これを回避するにはどうすればよいでしょうか? 確認して操作する前にロックを取得し、その後ロックを解放する必要があります。つまり、最初のスレッドにifブロックに入り、すべてのアクションを実行させ、z を変更してから、次のスレッドに同じことを行う機会を与える必要があります。ただし、 z が5 になる ため、次のスレッドはifブロックに入りません。

// Acquire the lock for z
if (z < 5) {
   z = z + 5;
}
// Release z's lock
===================================================

結論の代わりに

最後まで読んでくださった皆さんに感謝を言いたいです。遠かったけどよく耐えたね!もしかしたら、すべてが明らかではないかもしれません。これは正常です。私が初めて Java を勉強し始めたとき、静的変数とは何なのか全く理解できませんでした。でも大したことはありません。私はそれを寝て、さらにいくつかのソースを読み、そして理解しました。面接の準備は、実践的な質問というよりはむしろ学術的な質問です。したがって、各面接の前に、あまり頻繁に使用しない可能性のある事柄を見直して記憶をリフレッシュする必要があります。

いつものように、役立つリンクをいくつか紹介します。

読んでくださった皆様、ありがとうございました。またお会いしましょう :) 私の GitHub プロフィールJava Core に関する就職面接の質問と回答トップ 50。 パート 2 ~ 5
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION