Go to Contents Go to Java Page
J2SE 1.5 虎の穴
 
 

Concurrency Utilities その参 時間表現の巻

 
 
Tiger 時間をどのように表すべきか
 
 

非同期処理やリアルタイム処理を行うとき、よく出てくるのがタイムアウトです。ようするに、5 秒待つけどそれ以上は待てないよみたいなことです。

たとえば、Object#wait(long timeout) メソッドなどです。これは、timeout ミリ秒以内に notify されればメソッドを抜けますが、timeout ミリ秒たったら notify されていなくてもメソッドを抜けてしまいます。

しかし、マルチメディア系のアプリケーションなどリアルタイム性を求められるアプリケーションではミリ秒では不十分です。また、マシンのスペックがあがるにつれ、ミリ秒以下の時間を扱うのも容易になってきています。

一応、J2SE 1.4 でもナノ秒を扱うための Object#wait(long timeout, int nanos) というオーバロードされたメソッドが定義されています。しかしミリ秒とナノ秒を別々に指定しなければいけないなど、使い勝手はいまいちです。

また、Object#wait メソッドや Thread#sleep メソッドでナノ秒は指定できるのですが、ナノ秒単位の時刻の取得はできませんでした。

そこで、登場するのが ConcurrencyUtilities の TimeUnit クラスです。

また、ナノ秒単位の時間を取得することもできるようになっています。ナノ秒はSystem#nanoTimes メソッドを使用して取得します。このメソッドで取得できる値は、System#currentTimeMillis メソッドと違って特に意味はありません。開始時間と終了時間を差をとるような使い方が想定されています。

 

 
 
Tiger インスタンス化しないクラス
 
 

TimeUnit クラスは基本的にユーザはインスタンス化しません。定数として定義されているものを使用します。

時間単位 定数 捕捉
SECONDS  
ミリ秒 MILLISECONDS 1000 ms = 1 s
マイクロ秒 MICROSECONDS 1000 μs = 1 ms
ナノ秒 NANOSECONDS 1000 ns = 1 μs

たとえば java.util.concurrent.Future#get メソッドなどで使用される方法が TimeUnit クラスの基本的な使い方になります。

    Executor executor = Executor.newSingleThreadExecutor();
    Callable task = ...
 
    Future<String> future = Executors.execute(executor, task);
 
    // ミリ秒で 1000 待つ
    String resutl = future.get(1000, TimeUnit.MILLISECONDS);
 
    ...

基本的には TimeUnit クラスが表す時間単位とその単位で示される値のペアで使います。TimeUnit の Unit は単位なので、あくまでも時間の単位を表すためのクラスだということがお分かりでしょうか。

基本的にはこれだけです。終わり。

というわけにも行かないので、もう少し TimeUnit が備えている機能を見てみましょう。

 

 
 
Tiger 時間の変換
 
 

さて、1 秒は何ナノ秒でしょう。正解は 1 s = 1,000,000,000 ns です。変換自体はたいしたことありませんが、0 の数を間違えてしまいそうです ^^;;

こんなことはソフトでやってしまおうというのが TimeUnit#convert メソッドです。

サンプルのソース TimeUnitTest1.java

とりあえずいろんな変換をしてみました。

        System.out.println("0s = " 
                   + TimeUnit.MILLISECONDS.convert(0L, TimeUnit.SECONDS) + "ms");
        System.out.println("1s = "
                   + TimeUnit.MILLISECONDS.convert(1L, TimeUnit.SECONDS) + "ms");
        System.out.println("10μs = " 
                   + TimeUnit.MILLISECONDS.convert(10L, TimeUnit.MICROSECONDS) + "ms");
        System.out.println("100μs = "
                   + TimeUnit.MILLISECONDS.convert(100L, TimeUnit.MICROSECONDS) + "ms");
        System.out.println("100μs = "
                   + TimeUnit.MILLISECONDS.convert(1000L, TimeUnit.MICROSECONDS) + "ms");
        System.out.println("1000ns = "
                   + TimeUnit.MILLISECONDS.convert(10000L, TimeUnit.NANOSECONDS) + "ms");
        System.out.println("10000ns = "
                   + TimeUnit.MILLISECONDS.convert(100000L, TimeUnit.NANOSECONDS) + "ms");
        System.out.println("100000ns = "
                   + TimeUnit.MILLISECONDS.convert(1000000L, TimeUnit.NANOSECONDS) + "ms");

convert メソッドの第 1 引数は変換したい時間感覚です。そして、第 2 引数がその単位になります。ですから、1番始めは 0 秒をミリ秒に変換しています。

3 番目の例が、10 マイクロ秒をミリ秒に変換しています。出力するとどうなるでしょう。

0s = 0ms
1s = 1000ms
10μs = 0ms
100μs = 0ms
100μs = 1ms
1000ns = 0ms
10000ns = 0ms
100000ns = 1ms

10 マイクロ秒は 1 ミリ秒に足りないので 0ms になってしまいました ^^;; あくまでも整数で小数を使った表現にはならないことが注意点です。

convert メソッドの簡易版として toSeconds, toMillis, toMicros, toNanos メソッドが用意されています。

        System.out.println("10ms = "
                   + TimeUnit.MILLISECONDS.toSeconds(10L) + "s");
        System.out.println("10ms = "
                   + TimeUnit.MILLISECONDS.toMicros(10L) + "μs");
        System.out.println("10ms = "
                   + TimeUnit.MILLISECONDS.toNanos(10L) + "ns");

引数が先ほどと違って、オブジェクトが表わしている時間単位での値になります。したがって、結果は次のようになります。

10ms = 0s
10ms = 10000μs
10ms = 10000000ns

実際にはこれらのメソッドは convert メソッドを内部でコールしているだけのメソッドになります。

今までの例だと単位の変換で切捨てなのか、四捨五入なのかよく分かりません。切り上げしていないことは分かりますが。そこで、次のような例を書いてみました。

        System.out.println("500ms = " 
                   + TimeUnit.SECONDS.convert(500L, TimeUnit.MILLISECONDS) + "s");
        System.out.println("999ms = " 
                   + TimeUnit.SECONDS.convert(999L, TimeUnit.MILLISECONDS) + "s");
        System.out.println("1001ms = " 
                   + TimeUnit.SECONDS.convert(1001L, TimeUnit.MILLISECONDS) + "s");

微妙なところをついたつもりですが ^^;; 実行してみると次のようになりました。

500ms = 0s
999ms = 0s
1001ms = 1s

やはり、四捨五入ではなくて切り捨てのようです。

 

 
 
Tiger 寝る
 
 

Tiger になって Thread#sleep もナノ秒が使えるようになりました。Thread#sleep(long millis, int nanos) メソッドがオーバロードされています。

これを使ってもいいのですが、TimeUnit クラスでもスリープをすることができます。スリープするには TimeUnit#sleep(long timeout) メソッドを 使用します。Thread#sleep メソッドより引数が 1 つだけなので分かりやすいのではないでしょうか。具体的な使い方をみればもう少し利点が分かりやすいと思います。

サンプルのソース TimeUnitTest2.java

単にスリープするだけのサンプルです。static インポートを使用しているので、クラス名を書かなくても SECONDS などと書くことができます。

import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MICORSECONDS;
import static java.util.concurrent.TimeUnit.NANOSECONDS;
 
public class TimeUnitTest2 {
    public TimeUnitTest2() {
        try {
            long start = System.currentTimeMillis();
 
            SECONDS.sleep(1L);
            MILLISECONDS.sleep(100L);
            MICROSECONDS.sleep(100L);
            NANOSECONDS.sleep(100L);
 
            long end = System.currentTimeMillis();
            System.out.println("Sleep " + (end - start) + " ms");
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
 
        try {
            long start = System.nanoTime();
 
            SECONDS.sleep(1L);
            MILLISECONDS.sleep(100L);
            MICROSECONDS.sleep(100L);
            NANOSECONDS.sleep(100L);
 
            long end = System.nanoTime();
            System.out.println("Sleep " + (end - start) + " ns");
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        }
    }
 
    public static void main(String[] args) {
        new TimeUnitTest2();
    }
}

SECONDS.sleep(1L) は 1 秒のスリープ。MILLISECONDS.sleep(100L) は 100 ミリ秒のスリープということになります。分かりやすい表記だと思いませんか。

実際には Thread#sleep メソッドをコールしているだけなのですが、より使いやすくしたというだけです。

 

 
 
Tiger 待つ
 
 

スリープができるのですから、wait もできるのです。スリープと同じように Object#wait メソッドは Object#wait(long timeout, int nanos) がオーバロードされています。TimeUnit クラスを使用する場合は、TimeUnit#timedWait(Object object, long timeout) メソッドを 使用します。

サンプルのソース TimeUnitTest3.java

1,000 ミリ秒のタイムアウトでタイムアウトを行うサンプルです。

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import static java.util.concurrent.TimeUnit.MILLISECONDS;

public class TimeUnitTest3 {
    public TimeUnitTest3() {
        ExecutorService e = Executors.newSingleThreadExecutor();
        Executors.execute(e, new Task(this));
 
        synchronized (this) {
            try {
                MILLISECONDS.timedWait(this, 1000L);
                System.out.println("Woke Up.");
            } catch (InterruptedException ex) {
                ex.printStackTrace();
            }
        }

        e.shutdown();
    }
 
    class Task implements Runnable {
        private Object target;

        public Task(Object target) {
            this.target = target;
        }
 
        public void run() {
            try {
                Thread.sleep(500L);

                System.out.println("Wake Up!");
                synchronized (target) {
                    target.notifyAll();
                }
            } catch (InterruptedException ex) {
                System.out.println("Task is Canceled");
                return;
            }
        }
    }
 
    public static void main(String[] args) {
        new TimeUnitTest3();
    }
}

Object#wait メソッドと使い方は変わりません。使いやすくなっただけです。

同様に TimeUnit クラスには timedJoin メソッドもあります。でも、それほど使用頻度は高くないと思うので、ここでは省略。

 

 
 
Tiger おわりに
 
 

この解説では TimeUnit クラスを単体で使うことに的を絞って説明しましたが、実際には他のクラスと一緒に使うことの方が多そうです。たとえば

  • Future#get(long timeout, TimeUnit unit)
  • ExecutorService#awaitTermination(long timeout, TimeUnit unit)
  • ThreadPoolExecutor クラスのコンストラクタ
  • BlockingQueue#poll(long timeout, TimeUnit unit)

など java.util.concurrent パッケージでは大活躍です。大体の場合、タイムアウトの設定に使われるようです。

 

今回使用したサンプルはここからダウンロードできます。

参考

(Mar. 2004)

 
 
Go to Contents Go to Java Page