Java : アプリの多重起動を抑止 (FileLock)
アプリによっては、自分自身を同時に2つ以上起動させたくないことがあります。
そんなときに使えるのが FileLock クラスです。
本記事では、FileLock を使ったアプリの多重起動の抑止方法についてご紹介します。
注意
- 本記事のコード例は Windows 10 で実行しています。
概要
通常、Java アプリケーションは、同じアプリでも同時に複数起動できます。
例えば、次のような単純なコンソールアプリを作り、起動してみましょう。
public class Main {
public static void main(String[] args) throws InterruptedException {
System.out.println("start");
for (int i = 1; i <= 10; i++) {
System.out.println(" sleep ... " + i);
TimeUnit.SECONDS.sleep(1);
}
System.out.println("end");
}
}
このコンソールアプリを複数起動すると、次のようになります。
(GIFアニメーション)
並行して2つのアプリが起動できました。
これはこれで便利なこともありますが、同時に起動させたくないこともあるでしょう。
例えば、アプリが2つ起動していて同じファイルに同時に書き込むと…最悪ファイルが壊れてしまうかもしれません。
ファイル書き込み処理を、プロセスをまたいで排他制御するのも手間ですよね。
このような問題の解決方法の1つに、アプリの多重起動を抑止する、というものがあります。
もし同時に起動できるアプリを1つに制限すれば、複数のプロセスから同時に同じファイルを書き込むこともなくなり、プロセスをまたいだ排他制御も必要なくなります。
コード例
アプリの多重起動を抑止するために、今回は FileLock クラスを使います。
FileLock クラスは、複数起動したアプリ間で排他ロックを制御できます。
ロックには、物理的なファイルを使います。
さっそくコード例を見てみましょう。
ポイントは、FileChannel.tryLock メソッドです。
public class Main {
public static void main(String[] args) throws InterruptedException, IOException {
System.out.println("start");
final var path = Path.of("app.lock");
try (final var channel = FileChannel.open(
path, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
final var lock = channel.tryLock()) {
if (lock == null) {
System.out.println(" It's locked!");
} else {
for (int i = 1; i <= 10; i++) {
System.out.println(" sleep ... " + i);
TimeUnit.SECONDS.sleep(1);
}
}
}
System.out.println("end");
}
}
このコンソールアプリを並行して2つ起動すると、次のようになります。
(GIFアニメーション)
期待どおり、アプリの多重起動が抑止できていますね。
厳密にいうと、アプリ自体は一瞬2つ起動した状態になります。
しかし、アプリ起動直後に、
- 同じアプリがすでに起動していないか (ファイルロックされていないか) 確認
- もし起動していたら、メインの処理を実行せずに終了
という処理をします。
コードを少しずつ見ていきましょう。
final var path = Path.of("app.lock");
FileLock で使う、実際の物理的なファイルです。
今回は app.lock という名前のファイルを作ります。
try (final var channel = FileChannel.open(
path, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
final var lock = channel.tryLock()) {
...
}
FileLock を使うためには、まず FileChannel を open する必要があります。
オプションとして CREATE と WRITE を指定します。
これは、
- もしファイルが存在しなければ作成 (CREATE)
- 書き込み用 (WRITE)
という意味になります。
FileChannel.open(path, StandardOpenOption.CREATE, StandardOpenOption.WRITE)
次に FileChannel.tryLock メソッドで、FileLock を取得します。
もしすでにロック中の場合は null が返ります。
if (lock == null) {
System.out.println(" It's locked!");
} else {
...
}
よって、FileChannel.tryLock が null を返せば、
- すでに 別プロセス でアプリが起動していて排他ロックされている
と判断できます。
これでアプリの多重起動を制御できるわけですね。
try (final var channel = FileChannel.open(
path, StandardOpenOption.CREATE, StandardOpenOption.WRITE);
final var lock = channel.tryLock()) {
...
}
最後に、try-with-resources文 で channel と lock が解放されて、排他ロックも解除されます。
もう少し FileLock について知りたいかたは、下記の記事も合わせてご確認ください。
補足
プロセスを kill した場合は?
少し気になるのが、ファイルロック中にそのプロセスを kill したらどうなるのか? ということです。
app.lock ファイルが変な状態で残り続けて、それを手動で削除するまでアプリが起動できなくなる、とかなったら困りますよね。
実際に試してみましょう。
先ほどのコードを、プロセスIDを表示するように少しだけ修正します。
public class Main {
public static void main(String[] args) throws InterruptedException, IOException {
System.out.println("start");
final var pid = ProcessHandle.current().pid();
System.out.println("pid = " + pid);
...
}
}
(GIFアニメーション)
プロセスを kill すると、期待どおり排他ロックも解除されました。
私が確認した限りですが、プロセスが kill されても問題なさそうです。
Java VM が、うまいこと後処理してくれているのかな、と推測しています。
注意:Windows 10 で確認した結果です。
プラットフォームの依存性
FileLock クラスは、プラットフォームに依存します。
API仕様にも明記されています。
本当に例えばですが、Windows では想定どおりに動いたけど、Linux だとうまく動かなかった…ということもありえます。
Java は基本的にプラットフォームには依存しません。
それは Java の大きなメリットですが、FileLock は例外の1つというわけですね。
よって、運用するプラットフォーム上で事前に動作確認することはとても大事です。
まとめ
アプリの起動を1つに制限することで、解決する問題もあります。
例えば、同じアプリを複数起動すると、複数のプロセスから同じファイルに同時に書き込みが発生してよくない…というようなケースです。
アプリの多重起動を抑止する方法はいくつかあると思いますが、本記事では FileLock クラスを使った方法をご紹介しました。
本記事が、みなさまの一助になれば幸いです。