LoginSignup
15

More than 5 years have passed since last update.

参考メモ/Javaでデーモン・スレッドを作るメモ

Posted at

以前から Thread.isDaemon()Thread.setDaemon() メソッドが気になっていたのでメモです。
「デーモン・スレッド」については java.lang.Thread クラスのAPIドキュメントを参照。

「デーモン・スレッドではないスレッド」は「ユーザー・スレッド」と表記されてたりもするので、以降、その表現を使ってます。
ざっくり言うと、意図的にsetDaemon()を呼ばない限りは、アプリが作るスレッドは全部ユーザー・スレッドです。

$ cat /etc/centos-release
CentOS Linux release 7.2.1511 (Core) 

$ java -version
java version "1.8.0_77"
Java(TM) SE Runtime Environment (build 1.8.0_77-b03)
Java HotSpot(TM) 64-Bit Server VM (build 25.77-b03, mixed mode)

デフォルトのユーザー・スレッドのデモ

NoSetDaemon.java
import java.util.concurrent.*;

public class NoSetDaemon implements Runnable {
    final int num;
    public NoSetDaemon(int n) {
        this.num = n;
    }
    @Override
    public void run() {
        int c = 0;
        while (true) {
            System.out.println(Thread.currentThread().getName() + ":" + this.num + ":" + c);
            c++;
            try {
                Thread.sleep(1000);
            } catch(Exception ignore) {
            }
        }
    }
    public static void main(String[] args) {
        (new Thread(new NoSetDaemon(10))).start();
        ExecutorService s = Executors.newSingleThreadExecutor();
        s.submit(new NoSetDaemon(20));
        try {
            Thread.sleep(3000);
        } catch(Exception ignore) {
        }
        System.out.println("end of main():" + Thread.currentThread().getName());
    }
}

コンパイル&実行

$ javac NoSetDaemon.java 
$ java NoSetDaemon
Thread-0:10:0
pool-1-thread-1:20:0
Thread-0:10:1
pool-1-thread-1:20:1
Thread-0:10:2
pool-1-thread-1:20:2
Thread-0:10:3
end of main():main  ## main()が終了後もユーザー・スレッドは残り、JVMは終了しない。
pool-1-thread-1:20:3
Thread-0:10:4
pool-1-thread-1:20:4
Thread-0:10:5
pool-1-thread-1:20:5
Thread-0:10:6
pool-1-thread-1:20:6
^C  ## JVMを終了するためキーボードから Ctrl-C (SIGINT) 送信

デーモン・スレッドのデモ

ExecutorServiceを使う場合は、java.util.concurrent.ThreadFactory インターフェイスの実装クラスインスタンスを渡せるので、ThreadFactory.newThread() の中で setDeamon すればデーモン・スレッドを作れます。

SetDaemon.java
import java.util.concurrent.*;

public class SetDaemon implements Runnable, ThreadFactory {
    final int num;
    public SetDaemon(int n) {
        this.num = n;
    }
    @Override
    public void run() {
        int c = 0;
        while (true) {
            System.out.println(Thread.currentThread().getName() + ":" + this.num + ":" + c);
            c++;
            try {
                Thread.sleep(1000);
            } catch(Exception ignore) {
            }
        }
    }
    @Override
    public Thread newThread(Runnable r) {
        Thread t = new Thread(r);
        t.setDaemon(true);
        return t;
    }
    public static void main(String[] args) {
        Thread t = new Thread(new SetDaemon(10));
        t.setDaemon(true);
        t.start();
        ExecutorService s = Executors.newSingleThreadExecutor(new SetDaemon(0));
        s.submit(new SetDaemon(20));
        try {
            Thread.sleep(3000);
        } catch(Exception ignore) {
        }
        System.out.println("end of main():" + Thread.currentThread().getName());
    }
}

コンパイル&実行

$ javac SetDaemon.java 
$ java SetDaemon
Thread-0:10:0
Thread-1:20:0
Thread-0:10:1
Thread-1:20:1
Thread-0:10:2
Thread-1:20:2
Thread-0:10:3
end of main():main
Thread-1:20:3

main()終了後にデーモン・スレッドも終了し、JVMも終了してる。

ユーザー・スレッドが良いか、デーモン・スレッドが良いか?

一概にどちらが良い、というのは言えなくて、アプリケーションの要件と、スレッドで処理する内容によってくると思います。
ThreadクラスのAPIドキュメントにJVM終了時の挙動への影響も解説されてますので、こちらを参考にケースバイケースで選択することになるかと思います。

非常に単純化した例では、以下の様な使い分けが思いつきます。

  • 通信系のサービスを作る場合には、JVM終了時にまだ通信途中のスレッドまでいきなり終了してしまうようなデーモン・スレッドですと都合が悪いかもしれません。
  • 逆にデスクトップ・アプリケーションなどで、ユーザからの強制終了を受け付けたい場合は、デーモン・スレッドにすることで(データの不整合などが発生することを受け入れたうえで)強制終了を行うこともできます。

とはいえ、実際のアプリ開発となると「終了処理」一つとってもいろいろと考慮事項はあると思いますので、マルチスレッドプログラミングを勉強しつつ、ベストな選択をしたいところであります。


参考:

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
15