スレッドを実装する3つの方法のまとめ
最近、Javaのスレッド関連の問題の実装を見てきましたが、ここで方法の小さな要約のスレッドの実装では、メモとして、後で表示するのは簡単です。
糸通しには通常3つの方法があります:
Threadクラスを継承し、そのrun()メソッドをオーバーライドします。
Runnableインターフェイスを実装し、そのrun()メソッドを実装しています。
このメソッドは、Callable インターフェイスを実装し、その call() メソッドを実装します。
I. スレッドクラスの継承
Threadクラスでスレッドを作成するための最も重要なメソッドは2つあります:
public void start();
public void run();
Threadクラスを使ってスレッドを作成するには、Threadを継承し、Threadのrunメソッドをオーバーライドし、親スレッドのrunメソッドが例外を投げなければ、子クラスも例外を投げません。
サンプルコード1はスレッドを使用していません。
public class ThreadTest01 {
public static void main(String[] args) {
Processor p = new Processor();
p.run();
method1();
}
private static void method1() {
System.out.println("--------method1()----------");
}
}
class Processor {
public void run() {
for (int i=0; i<10; i++) {
System.out.println(i);
}
}
}
実施結果
0
1
2
3
4
5
6
7
8
9
--------method1()----------
つまり、method1が実行される前にrunメソッドが完全に実行され、method1が実行される前に前のメソッドが戻るのを待たなければなりません。
そのような実装の欠点は何ですか?
シーケンシャルに実行されるため、プログラムの実行効率が大幅に低下します。複数のコード断片を同時に実行することはできません。これは、マルチスレッド並行処理が達成しようとするものです。
サンプルコード2 スレッドの使用
public class ThreadTest02 {
public static void main(String[] args) {
Processor p = new Processor();
/*
メソッドが手動で呼び出された場合,
runを使ってシーンを開始することはできない。
run は普通のメソッド呼び出しである。
*/
//p.run();
/*
スレッドを開始するには、runを直接呼び出すのではなく、startを使う。,
start スレッドをすぐに実行するのではなく、スレッドを準備完了状態にする。
スレッドの実際の実行は、Javaのスレッドスケジューリング機構によって行われる。
*/
p.start();
//スレッドは一度しか起動できず、何度も起動できない。
//p.start();
method1();
}
private static void method1() {
System.out.println("--------method1()----------");
}
}
class Processor extends Thread {
/*
Threadのrunメソッドをオーバーライドする。
このメソッドは、Javaのスレッドスケジューリング機構によって呼び出される。
このメソッドは手動で呼ぶべきではない
*/
public void run() {
for (int i=0; i<10; i++) {
System.out.println(i);
}
}
}
実施結果
--------method1()----------
0
1
2
3
4
5
6
7
8
9
出力結果を見ればわかるように、method1()メソッドは逐次実行されるのではなく、同時に数値が出力されるため、効率で見ればマルチスレッドの例の方が速いのですが、これは同時実行とも言えるためで、mthod1()メソッドは前の処理の完了を待たずに実行され、これを「非同期プログラミングモデル」と呼びます。これを「非同期プログラミングモデル」と呼びます。
では、なぜこのような結果になったのでしょうか?
これは、Javaスレッドのスケジューリング機構が含まれ、プログラムは2つのスレッドが含まれて1つは、メインスレッドであるメインスレッドは、他のユーザーが作成したp個のスレッドは、クラスのロードが完了したときに、メインスレッドの開始は、メインメソッドスタックフレームの実行を開始、トップダウンの実行順序のコードに従って、最初のProcessorオブジェクトpのインスタンスを作成するには、p.startの実装が続く(メソッドが実装されていない、この時点で両方のスレッドが開始されている、Javaのスレッドスケジューリングのルールに従って、2つのスレッドは、プログラムのタイムスライスの実行をつかむために始めた、このグラブはランダムであることに注意してください、つまり、必ずしも出力はmethod1メソッドが最初に実行され、実装後のforループ文の実行です。あなたは、異なる結果を確認するために数回実行することができます。
II. 実行可能インターフェースの実装
実際には、Thread オブジェクト自体が Runnable インタフェースを実装していますが、一般的には、Runnable インタフェースを使用して直接マルチスレッドプログラムを記述することをお勧めします。
サンプルコード3
public class ThreadTest03 {
public static void main(String[] args) {
//Processor r1 = new Processor();
/*
ポリモーフィズムを使って、親型参照は子型オブジェクトを指す。,
なぜなら、この方法でRunnableインターフェースのメソッドを呼び出せるからだ。
*/
Runnable r1 = new Processor();
//上で説明した理由から、runメソッドを直接呼び出すことはできない。
//p.run();
//スレッド・オブジェクトを作成し、r1オブジェクトをパラメータとして
Thread t1 = new Thread(r1);
//スレッドを開始する
t1.start();
method1();
}
private static void method1() {
System.out.println("--------method1()----------");
}
}
//実行可能インターフェースの実装
class Processor implements Runnable {
//Runnableでrunメソッドを実装する
public void run() {
for (int i=0; i<10; i++) {
System.out.println(i);
}
}
}
実施結果
--------method1()----------
0
1
2
3
4
5
6
7
8
9
スレッドの実装方法が違うだけで、理由は同じです。
III.Callableインターフェースの実装
上記の2つのスレッド実行方法にどのような欠点があるか観察してください。明らかに、上記2つのスレッドのunメソッドの実行には戻り値がなく、実際にスレッド実行の戻り結果を取得する必要があります。それなら、3番目のスレッドの実装を使用することを検討できます。
利点:スレッドの実行結果を取得できます。デメリット:スレッドの実行結果を取得する際に、現在のスレッドがブロックされるため、効率が悪い。
サンプルコード 4-1 匿名内部クラスの使用
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadTest04 {
public static void main(String[] args) throws Exception {
// ステップ1:「未来のタスク・クラス」オブジェクトを作成する。
// パラメータは非常に重要で、Callableインターフェースの実装クラス・オブジェクトを与える必要がある。
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception { // call()メソッドはrunメソッドと同等である。このメソッドだけが戻り値を持つ
// スレッドはタスクを実行し、実行後には実行結果があるかもしれない。
// 実行をシミュレートする
System.out.println("call method begin");
Thread.sleep();//現在のスレッドは10秒間スリープする
System.out.println("call method end!");
int a = 100;
int b = 200;
return a + b; //オートボクシング
}
});
// スレッド・オブジェクトを作成する
Thread t = new Thread(task);
// スレッドを開始する
t.start();
// これがメイン・メソッドで、メイン・スレッドにある。
// メイン・スレッドで、tスレッドの戻り結果を得るには?
// get()メソッドの実行によって「現在のスレッドがブロックされる」。
Object obj = task.get();
System.out.println("スレッド実行結果:" + obj);
// mainメソッドの終了を待つ必要がある。
// getメソッドは、他のスレッドの実行結果を受け取るためのものである。
// 別のスレッドの実行に時間がかかる。
System.out.println("hello world!");
}
}
実施結果
call method begin
call method end!
スレッド実行結果:300
hello world!
サンプルコード 4-2 では、匿名内部クラスを使用していません。
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadTest01 {
public static void main(String[] args) throws Exception {
//Callableインターフェース実装クラスのインスタンス化オブジェクトを作成する
CallableImpl callable = new CallableImpl();
// ステップ1:「未来のタスク・クラス」オブジェクトを作成する。
// パラメータは非常に重要で、Callableインターフェースの実装クラス・オブジェクトを与える必要がある。
FutureTask task = new FutureTask(callable);
// スレッド・オブジェクトを作成する
Thread t = new Thread(task);
// スレッドを開始する
t.start();
// メイン・メソッドはメイン・スレッドにある。
// メイン・スレッドで、tスレッドの戻り結果を得るには?
// get()メソッドの実行によって「現在のスレッドがブロックされる」。
Object obj = task.get();
System.out.println("スレッド実行結果:" + obj);
// mainメソッドの終了を待つ必要がある。
// getメソッドは、他のスレッドの実行結果を受け取るためのものである。
// 別のスレッドの実行に時間がかかる。
System.out.println("hello world!");
}
}
//Callableインターフェイスを実装する
class CallableImpl implements Callable{
@Override
public Object call() throws Exception { // call()メソッドはrunメソッドと同等である。このメソッドだけが戻り値を持つ
// スレッドはタスクを実行し、実行後には実行結果があるかもしれない。
// 実行をシミュレートする
System.out.println("call method begin");
Thread.sleep();//現在のスレッドは10秒間スリープする
System.out.println("call method end!");
int a = 100;
int b = 200;
return a + b; //オートボクシング
}
}
実施結果
call method begin
call method end!
スレッド実行結果:300
hello world!





