Javaのsleepとは?プログラムを一時停止するThread.sleepの使い方
プログラムの中で少し待ちたいことはよくあります。Javaでプログラムを「待たせる」方法の一つに、指定した時間だけ停止させるThread.sleepがあります。
ここで「停止」と一言で書きましたが、Javaプログラミングの初心者が「停止ってこういうことじゃないかな?」と漠然と思っている状態とは多分違います。きちんと理解するには、Javaのマルチスレッドプログラミングの知識が少しだけ必要になるのです。
この記事では、プログラムを待たせるThread.sleepの使い方と、Javaでのスレッドやマルチスレッドのちょっとした知識を、初心者向けにお伝えします。
※この記事のサンプルは、Java 11の環境で動作確認しています
目次
1.Thread.sleepでプログラムの動作を一時的に止める
1-1.Thread.sleepの使い方
Javaプログラムの動作を一時的に止めるには、Threadクラスのstaticメソッドsleepを使うのが簡単です。引数のlongへは、止める時間をミリ秒で指定します。なお、ナノ秒単位でもできるようにオーバーロードされたメソッドもあります。
public static void sleep(long millis) throws InterruptedException パラメータ: millis - ミリ秒単位のスリープ時間の長さ 例外: IllegalArgumentException - millisの値が負の場合 InterruptedException - 何らかのスレッドが現在のスレッドに割り込んだ場合。 この例外がスローされると、現在のスレッドの割込みステータスはクリアされる。
Thread.sleepの使い方は以下のような感じです。
try { Thread.sleep(10000); // 10秒(1万ミリ秒)間だけ処理を止める } catch (InterruptedException e) { }
sleepはtry-catchとセットになるのが普通です。sleepは検査例外(catchしないとコンパイルエラーになる例外)のInterruptedExceptionをthrowするかもしれないからです。ただ、sleep中にこの例外が発生しても何もすることはないのが普通なので、catchが空なのもごく普通です。
1-2.本当に止まっているか測ってみよう
では、本当に止まっているか測ってみます。確かに10秒後の時刻が二回目に表示されましたね。
import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; class SleepSample { public static void main(String[] args) { System.out.println(DateTimeFormatter.ISO_LOCAL_TIME.format(LocalDateTime.now())); try { Thread.sleep(10000); // 10秒(1万ミリ秒)間だけ処理を止める } catch (InterruptedException e) { } System.out.println(DateTimeFormatter.ISO_LOCAL_TIME.format(LocalDateTime.now())); } }
実行結果
23:44:01.4868859 23:44:11.5069025
2.【参考】Javaでのスレッド・マルチスレッドの考え方
サンプルのとおり、Thread.sleepを使うと一時的にプログラムの動きが止まります。では、なぜ、どういう仕組みで止まるのでしょうか。
それに、mainメソッドの中で、自分でスレッドなるモノを作って動かした覚えはありませんよね。でもThread.sleepをすると実際にプログラムが止まるのは事実です。では、どこの誰がそのスレッドを作って、スレッド上でプログラムを実行し、スレッドを一時的に止めているのでしょうか。
その理解には、Javaでのスレッドやマルチスレッドの考え方を学ぶ必要があります。この章では、Thread.sleepの動きを理解するために必要な最低限の知識を簡単にお伝えします。
2-1.Javaを実行すると必ず一つはスレッドが作られる
Javaが実行される時には必ず一つはスレッドがあります。そして、そのスレッドはJava仮想マシンが管理します。いわゆるメインスレッド(Main thread)と呼ばれるものがJava仮想マシンにより必ず作られ、そのスレッドがクラスのmainメソッドに書かれた処理を実行するのです。
例えば、mainメソッドがないクラスを無理矢理実行すると以下のようなエラーが出ます。その中にある“main”こそが、まさにメインスレッドのことなのです。
java.lang.NoSuchMethodError: main Exception in thread "main"
つまり、Thread.sleepを呼び出すということは、Java仮想マシンに今このプログラムを動かしているスレッドをちょっと止めてくれ…と依頼をすることです。これはJavaのライブラリの世界ではなく、もっと深い、Java仮想マシンの世界で行われます。ですから、Thread.sleepはnativeメソッドだったりするのです。
// Java 11のソースコードからの抜粋 public static native void sleep(long millis) throws InterruptedException;
2-2.Thread.sleepをしても、他のスレッドは動き続ける
Javadocにあるとおり、Thread.sleepで止まるのは現在実行されているスレッド…つまりThread.sleepを呼び出したスレッドです。もし、別のスレッドを作って動かしていたなら、それらのスレッドは止まりません。Thread.sleepはJavaのプログラム全体やJava仮想マシン自体を止めるものではないのですね。
そして、メインスレッドの実行が終わることと、Java仮想マシンの実行が終わることは、必ずしも同じことではありません。Java仮想マシンが終わるのは「全てのスレッド」が終わった時なので、メインスレッドとは別にスレッドを作って動かしっぱなしにしていれば、Java仮想マシンはずっと終わらないのです。
ですので、「mainメソッドが終わったはずなのに、どうにもJavaが終わらないなぁ」という事象が発生した場合は、できるならデバッガでスレッドの一覧を表示してみましょう。きっと、あなたが終了し忘れているスレッドがこっそり動いているはずですよ。
2-3.InterruptedExceptionは他のスレッドからの割り込みを表す
Javaのプログラムは全てJava仮想マシンの管理下にあります。それはスレッドも例外ではなく、スレッド間の通信をするメソッドにはJava仮想マシンが関わります。その一つが、別のスレッドから「割り込み」があったことをスレッドに伝えるThread.interruptです。
InterruptedExceptionは、スレッドが何かをしている時に別のスレッドから割り込みがあったよと、スレッドに教えてあげるためにあるものです。ですから、sleep以外にも例えばObject.waitによりスレッドが止まっている時も、他のスレッドからの働きかけによりInterruptedExceptionが発生しえます。
また、スレッドへの割り込みそのものは、sleepなどで止まっている状態でなくても発生しえます。ですから、割り込まれたかどうかをスレッド自身が把握するために、Thread.interruptedやisInterruptedなどのメソッドが用意されているのです。
3.まとめ
Javaでプログラムの動きを一時的に止める時は、Thread.sleepを使ってミリ秒やナノ秒単位で止められます。動きが止まるのはThread.sleepを実行したスレッドだけで、もし他のスレッドを作って動かしていたなら、それらのスレッドは動き続けますので注意しましょう。
Javaでは、プログラム中でスレッドを作って使っているつもりがなくても、実はこっそり使っています。それがメインスレッドです。この知識があるのとないのとでは、Javaの動き方への理解が全然違います。何となくでもいいので、こういうことなんだと覚えておくと、役に立つことがあると思いますよ!!
なお、マルチスレッドはプログラムの性能を向上させる手段の一つです。ですが、ノンブロッキングI/Oなどのように、処理が終わったら呼び出すという仕組みも全体の性能向上のためには有力な方法です。プログラムのボトルネックの原因をしっかり把握し、最適な対応手段を選べるようになりたいですね。
コメント