Javaプログラミングで避けるべき9つの間違い
Javaでのプログラミングで避けるべき間違いとは?次の記事では、この質問にお答えします。
Javaの並行処理に関するブログ・シリーズの最初の部分をお読みください。次の記事では、スレッドとプロセスの違い、スレッドプール、エクゼキュータなどについて詳しく見ていきます!
一般的に、従来のプログラミング・アプローチは逐次的である。プログラムではすべてが一歩ずつ進んでいく。
並列処理とは、複数のタスクを同時に実行する能力のことだ。
以下のような高度なトピックについて議論する。 の並行性 ジャワ やマルチスレッドに対応するためには、共通の定義を決めておく必要がある。
基本的なことから始めよう。非シーケンシャルな世界では、2種類の並行性表現がある。
スレッドである。プロセスとは、実行中のプログラムのインスタンスである。通常は、他のプロセスから分離されている。
オペレーティング・システムは、各プロセスにリソースを割り当てる役割を担っている。さらに
スケジュールを立て、管理する。
スレッドはプロセスの一種であるが、より低レベルであるため、ライトスレッドとも呼ばれる。複数のスレッドを1つの
プロセスである。ここでプログラムは、スケジューラーとして、またスレッドのコントローラーとして機能する。こうすることで、個々のプログラムが
複数のタスクを同時にこなす。
スレッドとプロセスの基本的な違いは分離レベルです。プロセスは独自の
リソースを共有し、スレッドは他のスレッドとデータを共有する。これはエラーが発生しやすい手法のように思えるかもしれないが、実際にそうなのだ。その通りである。
しかし、それはこの記事の範囲外なので、ここでは触れないことにしよう。
プロセス、スレッド......それはいいとして、同時実行とはいったい何だろう?並行処理とは、複数のタスクを同時に実行できることを意味する。
時間である。それは、それらのタスクが同時に実行されなければならないという意味ではない。 ジャベイのコンカレ もしない。
は、マルチCPU、あるいはマルチコアを必要とする。シングルコア環境では
コンテキストの切り替え。
同時実行に関連する用語にマルチスレッディングがある。これは、複数のタスクを同時に実行できるプログラムの機能である。すべてのプログラムがこの方式を採用しているわけではないが、採用しているものはマルチスレッドと呼ぶことができる。
あと1つだけ定義がある。非同期とは、プログラムがノンブロッキング処理を行うことを意味する。
タスクを開始し、その反応を待つ間に他のことをする。応答があれば、それに反応することができる。
デフォルトでは Javaアプリケーション は1つのプロセスで実行される。そのプロセスには メイン()
方法
を使うことができる。しかし、前述したように、1つのアプリケーションの中で複数のスレッドのメカニズムを活用することは可能である。
プログラムだ。
スレッド
は ジャワ クラスでマジックが起こる。これは前述したスレッドのオブジェクト表現である。には
独自のスレッドを作成するには スレッド
クラスである。しかし、これは推奨される方法ではない。 スレッド
タスクを実行するメカニズムとして使用されるべきである。タスクは コード コンカレント・モードで実行したい。そのためには 走行可能
インターフェイスを使用している。
しかし、理論はもう十分だ。
いくつかの数値の配列があるとする。それぞれの配列について、配列内の数値の和を知りたい。ここで
このような配列がたくさんあり、それぞれが比較的大きいとする。このような状況では、並行処理を利用し、各配列を別々のタスクとして合計したい。
int[] a1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] a2 = {10, 10, 10, 10, 10, 10, 10};
int[] a3 = {3, 4, 3, 4, 3, 4, 2, 1, 3, 7};
実行可能なタスク1 = () -> { { 実行可能なタスク
int sum = Arrays.stream(a1).sum();
System.out.println("1.合計は: " + sum);
};
実行可能なタスク2 = () -> { {実行可能なタスク2
int sum = Arrays.stream(a2).sum();
System.out.println("2.合計は: " + sum);
};
実行可能なタスク3 = () -> { {実行可能なタスク
int sum = Arrays.stream(a3).sum();
System.out.println("3.合計は: " + sum);
};
new Thread(task1).start();
new Thread(task2).start();
new Thread(task3).start();
上のコードからわかるように 走行可能
は機能的インターフェースである。抽象メソッド 実行()
引数なしで。その 走行可能
インターフェースを実装する必要がある。
スレッドによって実行される。
タスクを定義したら、それを実行するスレッドを作成することができる。これは 新しいスレッド()
コンストラクタは
テイク 走行可能
を引数に取る。
最後のステップは 開始
新しく作成されたスレッドである。APIには 実行()
の方法 走行可能
そしてスレッド
.しかし、これはJavaで並行性を活用する方法ではない。これらの各メソッドを直接呼び出すと
が同じスレッドでタスクを実行する。 メイン()
メソッドが実行される。
タスクがたくさんある場合、それぞれに別のスレッドを作成するのは良いアイデアではない。スレッド スレッド
は
ヘビーウェイトな操作であり、新しいスレッドを作るよりも既存のスレッドを再利用する方がはるかに良い。
プログラムが多数の短命スレッドを作成する場合は、スレッド・プールを使用する方がよい。スレッド・プールには
実行可能だが現在アクティブでないスレッド。スレッドに 走行可能
をプールに呼び出すと、スレッドの1つが実行()
メソッド 走行可能
.タスク完了後、スレッドはまだ存在し、アイドル状態にある。
手動でスレッドを作成するよりもスレッドプールを利用した方がいいということはわかった。しかし、スレッドプールをどうやって利用するのだろうか?それは 執行者
クラスには、スレッド・プールを構築するための静的ファクトリー・メソッドがいくつかあります。例えば newCachedThredPool()
作成
このプールでは、必要に応じて新しいスレッドが作成され、アイドルスレッドは60秒間保持される。これに対してnewFixedThreadPool()
には固定されたスレッドセットが含まれ、その中でアイドルスレッドは無期限に保持される。
この例でどのように機能するか見てみよう。スレッドを手動で作成する必要はない。代わりにエクゼキュータ・サービス
これはスレッドのプールを提供する。そして、そのスレッドにタスクを割り当てることができる。最後のステップは、スレッド
プールを使ってメモリ・リークを回避する。前のコードの残りはそのままである。
ExecutorService executor = Executors.newCachedThreadPool();
executor.submit(task1);
executor.submit(task2);
executor.submit(task3);
executor.shutdown();
走行可能
は、並行タスクを作成する気の利いた方法のように見えるが、1つ大きな欠点がある。それは
値である。さらに、タスクが終了したかどうかを判断することはできない。また、完了したかどうかもわからない。
通常であれ例外的であれ。これらの病に対する解決策は 通話可能
.
通話可能
に似ている。 走行可能
ある意味では非同期タスクもラップしている。主な違いは
は値を返す。戻り値は 通話可能
インターフェイスはパラメータ化された型である。通話可能
を持つ機能的インターフェースである。 call()
メソッドをスローします。 例外
.
では、どのように活用できるか見てみよう。 通話可能
配列の問題である。
int[] a1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] a2 = {10, 10, 10, 10, 10, 10, 10};
int[] a3 = {3, 4, 3, 4, 3, 4, 2, 1, 3, 7};
callable task1 = () -> Arrays.stream(a1).sum();
Callable task2 = () -> Arrays.stream(a2).sum();
callable task3 = () -> Arrays.stream(a3).sum();
ExecutorService executor = Executors.newCachedThreadPool();
Future future1 = executor.submit(task1);
Future future2 = executor.submit(task2);
Future future3 = executor.submit(task3);
System.out.println("1. 合計は: " + future1.get());
System.out.println("2. 合計は: " + future2.get());
System.out.println("3. 合計は: " + future3.get());
executor.shutdown();
さて、どのようなものか見てみよう。 通話可能
に提出される。 エクゼキュータ・サービス
.しかし、一体何なのか? 未来
?未来
はスレッド間のブリッジとして機能する。各配列の合計は別々のスレッドで生成される。
その結果を メイン()
.
から結果を取得するには 未来
を呼ぶ必要がある。 ゲット
メソッドを使用する。ここで2つのことが起こる。まず
が実行した計算の結果である。 通話可能
が手に入る。それならすぐに手に入る。第二に、結果は
まだ準備ができていない。その場合 ゲット
メソッドは結果が出るまでブロックされる。
の問題である。 未来
それは「プッシュ・パラダイム」で動くということだ。そのため 未来
というボスのようにならなければならない。
タスクは終わったか?結果を出すまで。常にプレッシャーを感じながら行動することは
高い。もっといい方法は 未来
タスクの準備ができたら何をすべきか。残念だが、未来
それはできないが 計算可能未来
ができる。
計算可能未来
は「プル・パラダイム」で動く。タスクが完了したら、その結果をどうするかを指示することができる。それは
は非同期アプローチの例である。
計算可能未来
との相性は抜群だ。 走行可能
ただし 通話可能
.代わりに、タスクを計算可能未来
という形で サプライヤー
.
上記が我々の問題にどう関係するか見てみよう。
int[] a1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] a2 = {10, 10, 10, 10, 10, 10, 10};
int[] a3 = {3, 4, 3, 4, 3, 4, 2, 1, 3, 7};
CompletableFuture.supplyAsync(() -> Arrays.stream(a1).sum())
.thenAccept(System.out::println);
CompletableFuture.supplyAsync(() -> Arrays.stream(a2).sum())
.thenAccept(System.out::println);
CompletableFuture.supplyAsync(() -> Arrays.stream(a3).sum())
.thenAccept(System.out::println);
まず印象的なのは、このソリューションがいかに短いかということだ。その上、見た目もすっきりしている。
課題 完成可能な未来
が提供できる。 supplyAsync()
メソッドは サプライヤー
または runAsync()
その
テイク 走行可能
.コールバック - タスク完了時に実行されるコード - は次のように定義されます。 thenAccept()
メソッドを使用する。
ジャワ には、並行性に対するさまざまなアプローチがある。この記事では、このトピックにはほとんど触れなかった。
とはいえ、我々は以下の基本をカバーした。 スレッド
, 走行可能
, 通話可能
そして 呼び出し可能な未来
これは良い点を突いている
さらなるトピックの調査のために。