序文
マルチスレッドは基本的にインタビューの必需品です。インタビュアーは通常、簡単な質問から始めて、段階的に知識を掘り下げていきます。
たとえば、スレッドとは何か、スレッドとプロセスの違いから始めて、スレッドを作成する方法はいくつかあり、スレッドにはいくつかの状態があります。
次に、それは当然、スレッドプール、ロック、同期、およびJUCのさまざまな同時パッケージにつながります。次に、AQS、CAS、JMM、JVMなどの低レベルの原則を1つずつ導きます。
このセクションでは、他のことについては説明しません。スレッドを作成する方法がいくつかあることだけを説明します。
とてもシンプルに感じますか?そういうことではないですか?
実際にはそうではありません。インタビュアーに明確に説明し、私たち自身の理解を追加した場合にのみ、インタビューにポイントを追加できます。
テキスト
一般的に、より一般的に使用される次の4つの方法があります。最初に、それらの使用法を紹介します。次に、面接中に面接官の質問にどのように答えるかがより適切です。
1.Threadクラスを継承します
Threadクラスを継承し、そのrunメソッドを書き直すことで、スレッドを作成できます。
- まず、Threadクラスを継承するクラスを定義し、runメソッドをオーバーライドします。
- 次に、このサブクラスオブジェクトを作成し、startメソッドを呼び出してスレッドを開始します。
2.Runnableインターフェイスを実装します
Runnableを実装し、runメソッドを実装することで、スレッドを作成することもできます。
- まず、Runnableインターフェイスを実装するクラスを定義し、runメソッドを実装します。
- 次に、Runnable実装クラスオブジェクトを作成し、それをターゲットとしてThreadのコンストラクターに渡します。
- 最後に、startメソッドを呼び出してスレッドを開始します。
3. Callableインターフェースを実装し、Futureと組み合わせて実装します
- まず、Callable実装クラスを定義し、callメソッドを実装します。callメソッドには戻り値があります。
- 次に、FutureTask構築メソッドを介してCallable実装クラスを渡します。
- FutureTaskをThreadクラスのターゲットとして使用して、Threadスレッドオブジェクトを作成します。
- FutureTaskのgetメソッドを介してスレッドの実行結果を取得します。
4.スレッドプールを介してスレッドを作成します
ここでは、JDKに付属のExecutorsを使用してスレッドプールオブジェクトを作成します。
- まず、Runnable実装クラスを定義し、runメソッドをオーバーライドします。
- 次に、固定数のスレッドでスレッドプールを作成します。
- 最後に、スレッドオブジェクトはExecutorServiceオブジェクトのexecuteメソッドを介して渡されます。
スレッドを作成する方法はいくつありますか?
質問は、スレッドを作成する4つの方法の例を示したということですが、それは4つしかないということですか?
次の図に示すように、最初にJDKソースコードのThreadクラスの説明を見てみましょう。
新しい実行スレッドを作成する方法は2つあります
翻訳:新しい実行スレッドを作成するには2つの方法があります
ここで説明する2つの方法は、最初に紹介した2つの方法に対応しています。
ただし、これら2つのメソッドは最終的にThread.startメソッドを呼び出し、startメソッドは最終的にrunメソッドを呼び出すことがわかります。
違いは、Runnableインターフェイスの実装方法で、Threadクラスのrunメソッドが呼び出されることです。そのソースコードを見てみましょう、
このようにして、作成されたRunnable実装クラスオブジェクトがターゲットに割り当てられ、ターゲットのrunメソッドが実行されます。
Threadクラスを継承する方法を見ると、Threadのstartメソッドを呼び出してスレッドを開始する必要もあります。サブクラスはThreadクラスのrunメソッドをオーバーライドするため、このサブクラスのrunメソッドが最終的に実行されます。
したがって、同じことを言うこともできます。本質的に、スレッドを作成する唯一の方法は、Threadクラスを構築することです(そのサブクラスはThreadクラスと見なすこともできます)。
Threadクラスを作成する方法は2つあります。1つはThreadクラスを継承する方法で、もう1つはRunnableインターフェイスを実装する方法です。最終的には、Threadクラス(またはそのサブクラス)のオブジェクトを作成します。
FutureとFutureTaskを組み合わせて、Callableを実装する方法を見てみましょう。Threadクラスは、最終的に新しいThread(task)を介して構築されていることがわかります。
最後に、スレッドプールでは、スレッドの作成と管理のタスクをスレッドプールに実際に渡します。スレッドの作成は、スレッドファクトリクラスDefaultThreadFactoryを介して作成されます(ファクトリクラスをカスタマイズすることもできます)。このファクトリクラスの特定の実装を見てみましょう。
スレッド名、スレッドの優先度、スレッドグループ、デーモンスレッドであるかどうかなど、スレッドのデフォルト値を設定します。最後に、新しいThread()メソッドを使用してスレッドが作成されます。
したがって、要約すると。この質問に答えるとき、スレッドを作成する方法は基本的に1つしかないと言えます。それは、Threadクラスを作成することです。(この結論は、Java Concurrent Programming 78 Lectures-Xu Longxiから借用しています)
個人的な考え
しかし、ここで私はこの結論についていくつか質問をしたいと思います(あなたが異なる意見を持っているならば、あなたは記事の終わりにメッセージを残すことができます〜)。。。
個人的には、1、2、3、4種類あると言いたいのなら、実は大丈夫です。重要なことは、あなたが自分の根拠を述べ、それらの違いと共通点を伝えることができなければならないということです。よく話して、インタビュアーが頻繁にあなたにうなずきます。。
Threadクラスを作成してスレッドを作成する方法は1つしかないと言うのは、少し遠慮がちです。なぜなら、どのような方法から始めても、スレッドを作成したい場合は、最終的にThreadクラスを確実に構築するからです。(上記のメソッドを含めると、リフレクションを介しても、最終的にはnewInstanceでもありません)。
そして、この論理に従えば、どのようにオブジェクトを作成しても、Objectクラスを構築する方法は1つしかないと言えます。これは非常に正しいナンセンスであるため、この結論は少し退屈すぎるようです。
例としてArrayListを取り上げます。いくつかの方法でArrayListを作成する方法をお聞きします。あなたの80%はあなたが知っていることを誇示するように私に言うでしょう、
- 工法により、
List list = new ArrayList();
- によって
Arrays.asList("a", "b")
; - Java8が提供するStreamAPIを介して
List list = Stream.of("a", "b").collect(Collectors.toList());
- guavaサードパーティのjarパッケージを介して、
List list3 = Lists.newArrayList("a", "b");
待ってください、上にリストされているのは4つだけです。さて、ArrayListを作成する唯一の方法はArrayListクラスを構築することです。あなたは頭がおかしいわけではありません。
まるで、北京から上海に行く方法はいくつあるか聞いてみました。
あなたは車、電車、電車、高速鉄道、または飛行機に乗ることができると言います。
そうではありません。高速列車と高速列車はどちらも列車に属し、車と列車は車に属し、車と飛行機は輸送手段に属します。このように、唯一の方法は、輸送を利用することです。
これは正しくありません。交通機関を利用する必要はありません。そこを歩くことはできませんか?(目でテレポートできます。肌だけです〜)。
最後の結論は、唯一の方法があるということです。つまり、上海に行くことができます。これ、これ、これがなんという結論でしょう。。。
ですから、個人的にはスレッドを作成する方法は1つしかないと思います。
優れた技術的エッセイで、ほとんど私が議論の余地のあるエッセイに書いています。。。
慈悲深い人は慈悲深い人を見て、賢い人は知恵を見る。
最後に、私がインターネットから見た非常に興味深いトピックを見てみましょう。
興味深いトピック
質問:Runnableインターフェイスを実装するクラスは、デフォルトのrunメソッドを実行し、ターゲットが空ではないと判断し、最後にRunnableインターフェイスに実装されているrunメソッドを実行します。Threadクラスを継承すると、書き換えられたrunメソッドが実行されます。だから、今私は両方ともThreadクラスを継承し、Runnableインターフェイスを実装しています。次のプログラム、何を出力する必要がありますか?
public class TestThread {
public static void main(String[] args) {
new Thread(()-> System.out.println("runnable")){
@Override
public void run() {
System.out.println("Thread run");
}
}.start();
}
}
これはどのような操作なのか、一見わかりにくいかもしれません。
実際、上記のコードを分解すると、これがThreadの親クラスを継承し、親クラスのrunメソッドをオーバーライドするサブクラスオブジェクトであることがわかります。次に、親オブジェクトThreadで、Runnableインターフェイスの実装クラスがconstructionメソッドに渡され、runメソッドが実装されます。
startメソッドが実行されると、必然的に最初にサブクラスでrunメソッドが検索され、見つかった場合は直接実行されます。親クラスのrunメソッドは実行されないため、結果は次のようになります。スレッド実行。
サブクラスがrunメソッドを実装していないと想定される場合、サブクラスは親クラスでrunメソッドを検索し、親クラスのrunメソッドは、渡されたRunnableがあるかどうか(つまり、ターゲットが空かどうか)を判別し、ターゲットは空ではないため、 target.runメソッドが実行され、結果が出力されます:runnable。
したがって、上記のコードは複雑に見えますが、実際には非常に単純です。現象を通して本質を見ると、それはクラスの親子継承関係を調べることに他ならず、サブクラスオーバーライドは親クラスのメソッドがサブクラスオーバーライドのメソッドを優先することがわかります。
スレッドと組み合わせると、スレッドの操作メカニズムに慣れていない場合、混乱する可能性があります。