JavaRush /Java Blog /Random-JA /Java コア インタビューのトップ 50 の質問と回答。パート 3
Roman Beekeeper
レベル 35

Java コア インタビューのトップ 50 の質問と回答。パート 3

Random-JA グループに公開済み
Java コア インタビューのトップ 50 の質問と回答。パート1 Java コア インタビューのトップ 50 の質問と回答。パート2

マルチスレッド化

37. Javaで新しいスレッド(フロー)を作成するにはどうすればよいですか?

いずれにせよ、作成は Thread クラスを使用して行われます。しかし、ここには選択肢があるかもしれません...
  1. 私たちはから継承しますjava.lang.Thread
  2. java.lang.RunnableオブジェクトがコンストラクターThreadクラスを受け入れるインターフェイスを実装します。
それぞれについて話しましょう。

Thread クラスから継承します

これを機能させるために、クラスでは を継承しますjava.lang.Thread。これには meth が含まれており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.Runnablerun()ここのクラスでは、この例のように、このインターフェイスを実装し、メソッドを実装します。さらにオブジェクトを作成する必要があるだけです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. プロセスとスレッドの違いは何ですか?

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

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

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

40. スレッドのライフサイクルにはどのような状態がありますか?

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

41. スレッドを 2 回開始することは可能ですか?

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

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

はい、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
スレッドが作成されていないことがわかります。すべては通常の授業のように行われました。最初に最初のクラスのメソッドが機能し、次に 2 番目のメソッドが機能しました。

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

Java コア インタビューのトップ 50 の質問と回答。 パート 3 ~ 4デーモンスレッド(以下、デーモンスレッド)とは、他のスレッドと連携してバックグラウンドでタスクを実行するスレッドです。つまり、その仕事は、別の (メイン) スレッドと組み合わせてのみ実行する必要がある補助タスクを実行することです。ガベージ コレクター、ファイナライザーなど、自動的に動作するデーモン スレッドが多数あります。

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

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

Thread クラスで作業するためのメソッド

このクラスには、java.lang.Threadスレッド デーモンを操作するための 2 つのメソッドが用意されています。
  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. シャットダウンフックとは何ですか?

シャットダウンフックは、JVM (Java 仮想マシン) がシャットダウンする前に暗黙的に呼び出されるスレッドです。したがって、これを使用して、Java 仮想マシンが通常または突然シャットダウンしたときに、リソースをクリーンアップしたり、状態を保存したりできます。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 は同期を使用します。これにより、一度に 1 つのスレッドのみが動作できるようになります。同期は次の 3 つの方法で実現できます。
  • 同期方法
  • 特定のブロックを同期することで
  • 静的同期

メソッドの同期

同期メソッドは、共有リソースのオブジェクトをロックするために使用されます。スレッドが同期メソッドを呼び出すと、そのオブジェクトのロックが自動的に取得され、スレッドがタスクを完了するとロックが解放されます。これを機能させるには、 synchronizedキーワードを追加する必要があります。これがどのように機能するかを例で見てみましょう。
/**
* Пример, где мы синхронизируем метод. То есть добавляем ему слово 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 Не пишу Не Письмо

静的同期

静的メソッドを同期化すると、ロックはオブジェクトではなくクラスにかかります。この例では、synchronized キーワードを静的メソッドに適用して静的同期を実行します。
/**
* Вот 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マルチスレッド プログラミングでスレッド セーフを提供するために使用されます。これは、1 つの可変変数への変更が他のすべてのスレッドに表示されるため、一度に 1 つのスレッドで 1 つの変数を使用できるためです。キーワードを使用すると、volatile変数がスレッドセーフで共有メモリに保存され、スレッドがその変数をキャッシュに取り込まないことを保証できます。それはどのように見えますか?
private volatile AtomicInteger count;
変数に追加するだけですvolatile。しかし、これは完全なスレッド安全性を意味するものではありません... 結局のところ、変数に対する操作はアトミックではない可能性があります。Atomicただし、操作をアトミックに、つまりプロセッサによる 1 回の実行で実行するクラスを使用できます。このようなクラスの多くはパッケージ内にありますjava.util.concurrent.atomic

48. デッドロックとは

Java のデッドロックはマルチスレッドの一部です。デッドロックは、スレッドが別のスレッドによって取得されたオブジェクト ロックを待機しており、2 番目のスレッドが最初のスレッドによって取得されたオブジェクト ロックを待機している状況で発生する可能性があります。したがって、これら 2 つのスレッドは互いに待機し、コードの実行を続行しません。 Java コア インタビューのトップ 50 の質問と回答。 パート 3 ~ 5Runnable を実装するクラスがある例を見てみましょう。コンストラクターで 2 つのリソースを受け入れます。run() メソッド内では、それらのロックを 1 つずつ取得するため、このクラスの 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();
   }
}

/**
* Класс, который принимает два ресурса.
*/
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. デッドロックを回避するには?

デッドロックがどのように発生するかがわかっていることに基づいて、いくつかの結論を導き出すことができます...
  • 上の例に示されているように、デッドロックはロックのネストが原因でした。つまり、1 つのロックの中に別のロックが存在します。これは次の方法で回避できます。ネストする代わりに、新しい抽象化を最上位に追加し、ロックをより高いレベルに与え、ネストされたロックを削除する必要があります。
  • ブロックが多ければ多いほど、デッドロックが発生する可能性が高くなります。したがって、ロックを追加するたびに、それが本当に必要かどうか、新しいロックの追加を回避できるかどうかを考える必要があります。
  • を使用しますThread.join()。デッドロックは、あるスレッドが別のスレッドを待機しているときにも発生する可能性があります。join()この問題を回避するには、メソッドに時間制限を設定することを検討してください。
  • スレッドが 1 つであれば、デッドロックは発生しません ;)

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

実際のレースで車が実行する場合、レース用語のマルチスレッドではスレッドがレースで実行されます。しかし、なぜ?実行中で、同じオブジェクトにアクセスできるスレッドが 2 つあります。また、同時に状態の更新を試みることもできます。これまでのところ、すべてが明らかですよね?したがって、スレッドは実際に並列で (プロセッサーに複数のコアがある場合)、またはプロセッサーが短期間を割り当てる場合には条件付きで並列で動作します。そして、これらのプロセスを制御することはできないため、あるスレッドがオブジェクトからデータを読み取るときに、他のスレッドがそれを行う前にそのデータを変更する時間があるかどうかを保証することはできません。このような問題は、このテストと行為の組み合わせが実行されるときに発生します。それはどういう意味ですか?たとえば、if本体に条件自体が変化する式があります。つまり、次のとおりです。
int z = 0;

// проверь
if (z < 5) {
//действуй
   z = z + 5;
}
したがって、z がまだ 0 に等しいときに、2 つのスレッドが同時にこのコード ブロックに入り、一緒にこの値を変更する状況が発生する可能性があります。そして最終的には、期待値 5 ではなく 10 が得られます。これを回避するにはどうすればよいでしょうか? 実行前後にロックする必要があります。つまり、最初のスレッドが block に入るにはif、すべてのアクションを実行して変更し、zその後でのみ次のスレッドにこれを行う機会が与えられます。ただし、ブロックはすでに 5 に等しい ifため、次のスレッドはブロックに入りません。z
// получить блокировку для z
if (z < 5) {
   z = z + 5;
}
// выпустить из блокировки z
===================================================

アウトプットの代わりに

最後まで読んでくださった皆様に感謝の気持ちを伝えたいです。長い旅でしたが、よく頑張りましたね!すべてが明らかになるわけではないかもしれません。これで大丈夫です。Java を学習し始めた直後は、静的変数とは何なのか全く理解できませんでした。しかし、何もありませんでした。私はこの考えを持って眠り、さらにいくつかのソースを読んで、最終的に理解しました。面接の準備は実践的なものというよりも、学術的なものです。したがって、各面接の前に、あまり使用しない可能性のある事柄について繰り返し記憶を更新する必要があります。

そしていつものように、役立つリンク:

読んでくれてありがとう、また会いましょう) GitHub 上の私のプロフィール
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION