読者です 読者をやめる 読者になる 読者になる

ぺーぺーSEのブログ

備忘録・メモ用サイト。

Javaのマルチスレッドプログラミング(java.util.concurrent)

java.util.concurrentを使用したマルチスレッドプログラミング

ExecutorServiceインタフェース

ExecutorServiceインタフェースはExecutorインタフェースを拡張し、状態追跡やタスク処理の中断などを可能にしたインタフェース。
※ExecutorServiceインタフェースの機能はExecutorインタフェースの機能を包含するため、Executorインタフェースの説明は割愛。

メソッド 説明
execute() Executorより継承。タスク(Runnableインターフェースの実装クラス)を登録する。登録されたタスクはスレッドプールが空き次第順次実行される。
submit() タスク(Runnable、Callableインターフェースの実装クラス)を登録する。Futureオブジェクトを返す。
shutdown() シャットダウンを実行する。以前に登録したタスクは実行するが、新規タスクは受け入れられない。close処理と同様に忘れず実行すること。
shutdownNow() 現在実行中のタスクの中断を試み、待機中のタスクの処理を停止する。

ExecutorService実装クラス(スレッドプール)の取得方法

ExecutorService executorService = Executors.newSingleThreadExecutor();
ExecutorService executorService = Executors.newFixedThreadPool(int n);
ExecutorService executorService = Executors.newCachedThreadPool(int n);

ExecutorService実装クラス取得メソッドの説明

メソッド 説明
newSingleThreadExecutor 単一のスレッドで送信されたタスクを実行するExecutorを作成する。
newFixedThreadPool 指定した数のスレッドを使いまわしてタスクを処理する。
newCachedThreadPool 複数のスレッドでタスクを処理するが、もし処理の終了しているスレッドがあれば、そのスレッドを再利用する。 60秒使用されないスレッドは削除される。短時間で処理の終わるタスクを、大量に処理する場合に有用。

Runnableインターフェース

ExecutorServiceへ登録するタスクを実装するためのインターフェース。
「void run()」を実装したクラスを「ExecutorService.execute」や「ExecutorService.submit」の引数で渡して実行する。
返り値、throwsを指定することができない。

Callableインターフェース

ExecutorServiceへ登録するタスクを実装するためのインターフェース。
「T call() throws Exception」を実装したクラスを「ExecutorService.submit」の引数で渡して実行する。
任意の返り値、throwsを指定することができる。
返り値は「ExecutorService.submit」の返り値である「Future」オブジェクトから取得することができる。
ただし、返り値を取得する場合はタスクスレッドが終了するまで親スレッド(ExecutorService.submitを呼び出すスレッド)は待機することになるので注意が必要。

Futureインターフェース

Futureオブジェクトは、非同期計算のためのインタフェース。計算が完了したかどうかをチェックし、計算を待機し、計算結果を取得するためのメソッドが提供される。
「ExecutorService.submit」の返り値からオブジェクトを取得する。

メソッド 説明
boolean cancel(boolean mayInterruptIfRunning) このタスクの実行の取り消しを試みる。
V get() 必要に応じて、計算が完了するまで待機し、その後、結果を取得する。
V get(long timeout, TimeUnit unit) 必要に応じて、計算が完了するのを、指定された時間まで待機する。
boolean isCancelled() このタスクが通常どおり完了する前に、取り消された場合は true を返す。
boolean isDone() このタスクが完了した場合は、true を返す。

■実装例
この実装では「Future.get」を実行しているため、Taskが終了するまで親スレッド(Mainスレッド)は待つことになる。

public class Task implements Callable<Integer>{
    private String name;
    public Task(String name){ 
        this.name = name;
    }
    public Integer call() throws Exception{
        int successTask = 0;
        for(int i=0; i<3; i++){
            System.out.println(this.name + " execute Task" + i + ".");
            try {
                successTask++;
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return successTask++;
    }
    
    public static void main(String[] args){ 
        ExecutorService exesutorService = Executors.newFixedThreadPool(2);
        System.out.println("Tasks start !");
        for(int i=0; i<3; i++){
        	Future<Integer> future = null;
            try{
                future = exesutorService .submit(new Task("Task" + i));
                System.out.println(future.get()+"tasks success !");
            } catch (Exception e){
                System.out.println("Exception occured !");
                future.cancel(true);
            }
        }
        System.out.println("Sending tasks finish !");
        
        exesutorService .shutdown();
        System.out.println("ExecutorService shutdown.");
    }
} 

実行結果

Tasks start !
Task0 execute Task0.
Task0 execute Task1.
Task0 execute Task2.
3tasks success !
Task1 execute Task0.
Task1 execute Task1.
Task1 execute Task2.
3tasks success !
Task2 execute Task0.
Task2 execute Task1.
Task2 execute Task2.
3tasks success !
Sending tasks finish !
ExecutorService shutdown.

java.util.concurrentを使用したスケジューリングも含めたマルチスレッドプログラミング

ScheduledExecutorServiceインタフェース

スケジューリングを可能にするクラスは、java.util.concurrent.ScheduledExecutorServiceインタフェースを実装している。

メソッド 説明
schedule(Runnable command, long delay, TimeUnit unit) 指定した時間が経過したのちに第一引数で指定したタスクを実行する。時間の指定はTimeUnitクラスによって、秒、ミリ秒、マイクロ秒、ナノ秒の単位で定めることが出来る。
scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) タスクが送信された後、第二引数で指定した長さが経過すればタスク処理を開始する。その後、タスクは順に処理される。
scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) 指定した遅延時刻によって周期的に実行される点などscheduleAtFixedRate()メソッドと基本的には変わらないが、タスクの再実行のタイミングが異なる。

ScheduledExecutorService実装クラス(スレッドプール)の取得方法

ScheduledExecutorService sExecutorService = Executors.newSingleThreadScheduledExecutor();
ScheduledExecutorService sExecutorService = Executors.newScheduledThreadPool(int n);

ScheduledExecutorService実装クラス取得メソッドの説明

メソッド 説明
newSingleThreadScheduledExecutor 一つのスレッドでタスク処理を行う。
newScheduledThreadPool 指タスクを処理するスレッドの数を指定出来る。


その他マルチスレッドプログラミングの話題

volatile

スレッド間でフィールドを経由して値を共有する場合は、フィールド変数に「volatile」修飾子をつけておく必要がある。
スレッドは他のスレッドのフィールド値を参照する際、毎回参照するのではなく、最初のアクセスで自スレッドが使用するメモリ領域に値をコピーしておく。
そのため、フィールド変数の値が変更されても変更した値を参照していない。
これを回避するために参照するフィールドに「volatile」をつけておくことにより、毎回参照するようにする。

java.util.concurrent.atomic.AtomicBoolean

上記でvolatileの話題をあげたが、スレッド間で共有するフィールドがあって且そのフィールドがBooleanである場合、
volatileを使用しなくても原始性が担保されている「java.util.concurrent.atomic.AtomicBoolean」を使用するほうがよい。
http://e-class.center.yuge.ac.jp/jdk_docs/ja/api/java/util/concurrent/atomic/AtomicBoolean.html

参考:
http://www.techscore.com/tech/Java/JavaSE/Thread/index/