本記事では、『Oracle Certified Java Programmer, Gold SE 11』の出題範囲、『並列処理』について、黒本の内容を自分なりに解釈し、まとめていきます
内容が長くなってしまったため、二つに記事を分けています
対象者としては、以下の方を想定しています
- Java SE11 Goldの試験を受ける予定で、クラスとインターフェースの項目の理解を深めたい方
- Java SE11 Goldの範囲内で、クラスとインターフェースについて、おさらいしたい方
『Java SE8 Gold』の情報が多い中、『Java SE11 Gold』の内容があまり存在しないため、本記事を作成いたしました
また、このような記事を書いているものの、『Java SE11 Gold』取得に向けて勉強している身のため、その点はご容赦ください
並列処理について
『並列処理』では、複数の処理を同時並行で処理することができる
マルチスレッドで処理を行うには『java.lang.Threadクラス』のサブクラスを実装する必要があり、
「runメソッド」に行いたい処理を記述し、「start」メソッドを実行する
public class Hoge {
public static void main(String[] args) {
Thread thread = new Main2().new Fuga();
thread.start();
System.out.println("Hoge");
}
public class Fuga extends Thread {
@Override
public void run() {
System.out.println("Fuga");
}
}
}
Hoge
Fuga
Runnableインターフェース
新しいスレッドの作成は、『java.lang.Runnableインターフェース』を実現したクラスを、Threadクラスのコンストラクタに渡すことでも実現できる
public class Hoge {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Fuga");
}
});
thread.start();
System.out.println("Hoge");
}
Hoge
Fuga
ExecutorServiceインターフェース
先述のマルチスレッドプログラミングでは、実行したい処理の数だけ新しいスレッドを作成する必要があり、数が多くなればなるほど、スレッドの生成中に最初のスレッドの処理が終了している場合がある
『スレッドプール』では、複数個のスレッドを作成しておき、そのスレッドにタスクを与えて実行させスレッドを使いまわすため、スレッドの無駄遣いが無くなる
使用するには『java.util.concurrent.ExecutorService』または、『java.util.concurrent.ScheduledExecuterService』を利用する
これらの実装を取得するのが『java.util.concurrent.Executorsクラス』で、「ExecutorServiceインターフェース」を実現したインスタンスへの参照を戻す、以下のメソッドを持っている
メソッド名 | 概要 |
newSingleThreadExecutor | 新しいスレッドを一つだけ作成してプールする |
newFixedThreadExecutor | 生成したいスレッド数を引数で受け取り、指定した数のスレッドを保持するスレッドプールを作成する |
newCachedThreadPool | 一度生成されたスレッドは60秒間使用されなければ破棄される |
newSingleThreadScheduledExecutor | 新しいスレッドを一つだけ作成してプールし、メソッドを定期実行する |
newSingleThreadExecutor
スレッドIDを確認すると1つのスレッドを使いまわしているのがわかる
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Hoge {
public static void main(String[] args) {
ExecutorService single = Executors.newSingleThreadExecutor();
for(int i = 0; i < 5; i++) {
single.submit(() -> {
System.out.println(Thread.currentThread().getId());
});
}
}
15
15
15
15
15
newFixedThreadPool
スレッドIDを確認すると引数で渡した5つのスレッドを使いまわしているのがわかる
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Hoge {
public static void main(String[] args) {
ExecutorService fixed = Executors.newFixedThreadPool(5);
for(int i = 0; i < 5; i++) {
fixed.submit(() -> {
System.out.println(Thread.currentThread().getId());
});
}
}
19
17
16
15
18
newCachedThreadPool
5秒後の確認ではスレッドが使いまわされているが、65秒後の確認では新しいスレッドが生成されているのがわかる
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Hoge {
public static void main(String[] args) throws Exception {
ExecutorService cached= Executors.newCachedThreadPool();
for(int i = 0; i < 5; i++) {
cached.submit(() -> {
System.out.println(Thread.currentThread().getId());
});
}
Thread.sleep(1 * 5000);
System.out.println("5秒後");
for(int i = 0; i < 5; i++) {
cached.submit(() -> {
System.out.println(Thread.currentThread().getId());
});
}
Thread.sleep(1 * 65000);
System.out.println("65秒後");
for(int i = 0; i < 5; i++) {
cached.submit(() -> {
System.out.println(Thread.currentThread().getId());
});
}
}
17
18
15
16
19
5秒後
18
17
16
19
15
65秒後
20
21
21
22
23
ScheduledExecutorServiceインターフェース
『java.util.concurrent.ScheduledExecutorService』は『java.util.concurrent.ExecutorService』を拡張したインターフェースで、メソッドを定期的に実行するためのもの
『ScheduledExecutorService』にはいくつかのメソッドが存在する
メソッド名 | 概要 |
schedule | 指定した時間遅延させた後、指定した処理を1回実行する |
scheduleAtFixedRate | 初期の遅延時間とインターバル時間の二つを指定し、指定した処理を繰り返し実行する インターバルよりも処理の時間がかかった場合は、その処理の終了後即時で実行される |
scheduleWithFixedDelay | 初期の遅延時間とインターバル時間の二つを指定し、指定した処理を繰り返し実行する 処理の時間は関係なく、実行終了後インターバルの時間が確保される |
scheduleメソッド
scheduleメソッドの引数には、それぞれ以下を指定する
- java.util.Runnable型の実行したい処理
- long型の遅延させる時間
- 遅延させる時間の単位(「java.util.concurrent.TimeUnit」という列挙型を使用する。「HOURS」, 「MINUTES」, 「SECONDS」などが用意されている)
以下は1秒後に実行した例
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
public class Hoge {
public static void main(String[] args) throws Exception {
ScheduledExecutorService exe = Executors.newSingleThreadScheduledExecutor();
exe.schedule(() -> {
System.out.println("hoge");
exe.shutdown(); // スレッドプールを閉じる
}, 1, TimeUnit.SECONDS);
for (int i = 0; i <= 11; i++) {
System.out.println(i * 100 + "ms");
Thread.sleep(100);
}
}
}
0ms
100ms
200ms
300ms
400ms
500ms
600ms
700ms
800ms
900ms
hoge
1000ms
1100ms
scheduleAtFixedRateメソッド
scheduleAtFixedRateメソッドの引数には、それぞれ以下を指定する
- java.util.Runnable型の実行したい処理
- long型の遅延させる時間(初期)
- long型の遅延させる時間(インターバル)
- 遅延させる時間の単位(「java.util.concurrent.TimeUnit」という列挙型を使用する。「HOURS」, 「MINUTES」, 「SECONDS」などが用意されている)
以下は処理時間を1000ミリ秒までのランダムにし、1秒のインターバルで実行した例
乱数の表示が一定じゃないため、インターバルの時間が確保されていないのがわかる
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
public class Hoge {
public static void main(String[] args) throws Exception {
ScheduledExecutorService exe = Executors.newSingleThreadScheduledExecutor();
exe.scheduleAtFixedRate(() -> {
int r = new Random().nextInt(10);
System.out.print(r);
try {
Thread.sleep(r * 100);
} catch (InterruptedException e) {
// 処理なし
}
System.out.println("hoge");
}, 1, 1, TimeUnit.SECONDS);
for (int i = 0; i <= 100; i++) {
System.out.print(">");
Thread.sleep(100);
}
exe.shutdown();
}
}
>>>>>>>>>>9>>>>>>>>hoge
>9>>>>>>>>hoge
>2>>hoge
>>>>>>>3>>>hoge
>>>>>>5>>>>>hoge
>>>>9>>>>>>>>hoge
>7>>>>>>>hoge
>>6>>>>>>hoge
>>>>5>>>>hoge
>>>>>4>>>hoge
>>>>>>8hoge
scheduleWithFixedDelayメソッド
scheduleWithFixedDelayメソッドの引数には、scheduleAtFixedRateメソッド同様それぞれ以下を指定する
- java.util.Runnable型の実行したい処理
- long型の遅延させる時間(初期)
- long型の遅延させる時間(インターバル)
- 遅延させる時間の単位(「java.util.concurrent.TimeUnit」という列挙型を使用する。「HOURS」, 「MINUTES」, 「SECONDS」などが用意されている)
以下は処理時間を1000ミリ秒までのランダムにし、1秒のインターバルで実行した例
乱数の表示が一定であるため、インターバルの時間が確保されているのがわかる
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
public class Hoge {
public static void main(String[] args) throws Exception {
ScheduledExecutorService exe = Executors.newSingleThreadScheduledExecutor();
exe.scheduleWithFixedDelay(() -> {
int r = new Random().nextInt(10);
System.out.print(r);
try {
Thread.sleep(r * 100);
} catch (InterruptedException e) {
// 処理なし
}
System.out.println("hoge");
}, 1, 1, TimeUnit.SECONDS);
for (int i = 0; i <= 100; i++) {
System.out.print(">");
Thread.sleep(100);
}
exe.shutdown();
}
}
>>>>>>>>>>1>hoge
>>>>>>>>>9>>>>>>>>hoge
>>>>>>>>>9>>>>>>>>>hoge
>>>>>>>>>3>>>hoge
>>>>>>>>>7>>>>>>hoge
>>>>>>>>>9>>>>>>>>>hoge
>>>>>>>>>3>hoge
スレッドプール
『ScheduledExecutorService』は『newScheduledThreadPoolメソッド』を使用することで、スレッドプールとして扱うことができる
以下は3つのスレッドが1秒ずつ遅延しながら実行されている例
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
public class Hoge {
public static void main(String[] args) {
ScheduledExecutorService exe = Executors.newScheduledThreadPool(3);
exe.scheduleWithFixedDelay(() -> {
System.out.print("1");
}, 1, 1, TimeUnit.SECONDS);
exe.scheduleWithFixedDelay(() -> {
System.out.print("2");
}, 1, 1, TimeUnit.SECONDS);
exe.scheduleWithFixedDelay(() -> {
System.out.print("3");
}, 1, 1, TimeUnit.SECONDS);
Thread.sleep(10000);
exe.shutdown();
}
}
321321321312312312321312321
Futureインターフェース
Futureインターフェースを使用することで、スレッドを外部から操作することができる
submitメソッド引数無
スレッドの処理が完了すると『null』を返す
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
public class Hoge {
public static void main(String[] args) {
ExecutorService exe = Executors.newSingleThreadExecutor();
Future future = exe.submit(() -> {
System.out.println("fuga");
});
if (future.get() == null) {
System.out.println("hoge");
}
}
}
fuga
hoge
submitメソッド引数有
『null』以外の値を戻したい場合は、『submitメソッド』の第二引数に戻したい値を指定する
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
public class Hoge {
public static void main(String[] args) {
ExecutorService exe = Executors.newSingleThreadExecutor();
Future<String> future = exe.submit(() -> {
System.out.println("fuga");
}, "hoge");
System.out.println(future.get());
}
}
fuga
hoge
Callableインターフェース
「Futureインターフェース」の「getメソッド」では、「null」または、「固定の値」しか戻せない
それに対し、『Callableインターフェース』では、処理結果を戻したり、例外をスローするタスクを定義できる
処理結果を戻す例
1 + 1の結果を戻している
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
public class Hoge {
public static void main(String[] args) {
ExecutorService exe = Executors.newSingleThreadExecutor();
Callable<Integer> task = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
return 1 + 1;
}
};
Future future = exe.submit(task);
System.out.println(future.get());
}
}
2
例外をスローする例
import java.util.Random
import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
public class Hoge {
public static void main(String[] args) {
ExecutorService exe = Executors.newSingleThreadExecutor();
Callable<Integer> task = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
if (new Random().nextInt(1) != 10) {
throw new Exception("fuga");
}
return 1 + 1;
}
};
Future future = exe.submit(task);
try {
System.out.println(future.get());
} catch (ExecutionException e) {
System.out.println(e.getMessage());
}
}
}
fuga
おわりに
自分のアウトプットも兼ねて、Java SE11 Goldの試験で出題される、並列処理についてまとめました
間違っている点があった場合は、教えてくださると助かります
他の章についてもまとめていきますので、お待ちいただければと思います