理系学生日記

おまえはいつまで学生気分なのか

ExecutorService を使ったスレッドプール実験

そういえば先日、Executor をはじめて使った。昔のソースを眺めていると、生で Thread を使い、その Thread を配列にプールしていて自作スレッドプールを作成していたことが判明したのだけれど、この頃から比較すると随分と時代は変わったのだと実感した。まさに隔世の感がある。

ExecutorService

例えば、プール数が 2 のスレッドプールを作成して、そこに 4 つほどタスクを投げてみる。タスクの内容はどうでも良いので、ここではランダムな時間だけ sleep して、メッセージを出力するタスクにしよう。
これは以下の通り僅かなコードだけで実現できる。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorServiceTestMain {
  
  private static final int THREAD_NUM = 2;

  public static void main(String[] args) {
    ExecutorService executor = Executors.newFixedThreadPool( THREAD_NUM );
    
    for ( int i = 0; i < 4; ++i ) {
      executor.submit( new Runnable() {
        @Override
        public void run() {
          long sleepTime = (long)(Math.random() * 1000);
          System.out.printf("[Thread %02d] starts.\n", 
              Thread.currentThread().getId());
          
          try {
            Thread.sleep(sleepTime);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          System.out.printf("[Thread %02d] sleeps %d ms.\n", 
              Thread.currentThread().getId(), sleepTime);
        }
      });
    }
    executor.shutdown();
  }
}

この出力はというと以下のようになり、Thread 10 と 11 だけが、4 つのタスクを非同期に実行している様子が容易に見てとれる。

[Thread 10] starts.
[Thread 11] starts.
[Thread 11] sleeps 455 ms.
[Thread 11] starts.
[Thread 10] sleeps 925 ms.
[Thread 10] starts.
[Thread 11] sleeps 723 ms.
[Thread 10] sleeps 901 ms.

上記は Thread#sleep によって遅延を生じさせているけれど、この遅延を作成できる ExecutorService 実装も存在して、それが ScheduledExecutorService になる。もっとも、これは初期遅延を生じさせるのはオマケみたいなもので、どちらかといえば定期実行だったり、一定の間隔を置いて実行させるのに使われるものという認識でいた方が良い。
この ScheduledExecutorService を使うと、上記の処理は以下のようになるだろうか。

  • ScheduledExecutorServiceTestMain.java
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ScheduledExecutorServiceTestMain {
	
  private static final int THREAD_NUM = 2;

  public static void main(String[] args) {
    ScheduledExecutorService executor = Executors.newScheduledThreadPool(THREAD_NUM);
    
    for ( int i = 0; i < 4; i++ ) {
      long delayTime = (long)(Math.random() * 1000);
      
      Runner runner = new Runner(delayTime);
      executor.schedule(runner, delayTime, TimeUnit.MILLISECONDS);
    }
    executor.shutdown();
  }
}
  • Runner.java
public class Runner implements Runnable {
	private long delayTime = 0;
		
	public Runner(long sleepTime) { this.delayTime = sleepTime; }
	
	@Override 
	public void run() {
		System.out.printf("[Thread %02d] %4d ms delay\n", 
				Thread.currentThread().getId(), delayTime);
	}
}

この出力結果はというと、以下のようになった。

[Thread 11]    6 ms delay
[Thread 10]  237 ms delay
[Thread 11]  574 ms delay
[Thread 10]  739 ms delay

6 ms だけの遅延しかない Thread 11 が、実行を 237 ms 遅延させた Thread 10 より先に来ていることが確認できる。