排他制御とは
なんでそんな難しい言葉使うねんと。
ようは、同時に同じ処理呼びたくないよってことだと私はとらえています。
差異はあるかもしれないですが、とりあえず何がしたいかというと、
多重起動を防止するってことです。
なぜなら
テーブルの同じレコードを同時に編集しようとすると、おもくそ競合して、デッドロックになってしまうからです。
大まかな流れ
ファイルロックという手法を用います。
【手順】
-
ファイルをロックする
→ ファイルがロックされていた場合処理を終了
→ ロックされていなければ続行 -
ビジネスロジックを実行
-
ファイルロックを解除して終了
ね、簡単でしょう?
ファイルが既にロックされていた場合はOverlappingFileLockExceptionが発生するので、固有の処理でもなんでも好き放題ハンドリングできます
ということでサンプル
package test;
import java.io.File;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.StandardOpenOption;
import java.util.function.Consumer;
public class Test {
/** バッチ処理名 */
private static final String BATCH_NAME = "なんとかバッチ";
public static void main(String[] args) {
execute();
}
public static void execute() {
// 排他制御をしてビジネスロジックを起動
exclusiveController(job1, BATCH_NAME);
exclusiveController(job2, BATCH_NAME);
}
public static void exclusiveController(Consumer<String> job, String processName) {
File file = new File("ファイルパス");
try (FileChannel channel = FileChannel.open(file.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE);) {
// ファイルロックを試みる
FileLock lockedObject = channel.tryLock();
// 固有ビジネスロジックを実行する
job.accept(processName);
// ファイルロックの解除(channelがクローズされた場合、Java仮想マシン終了時もロック解除される)
lockedObject.release();
} catch (OverlappingFileLockException e) {
// ファイルがすでにロックされていた場合
} catch (IOException e) {
// IOエラー
}
}
/** 固有ビジネスロジック1 */
private static final Consumer<String> job1 = System.out::println;
/** 固有ビジネスロジック2 */
private static final Consumer<String> job2 = System.out::println;
}
-
ファイルロックが解除されるのは以下の3パターンです。
- FileLock#releaseを実行して手動でロック解除
- FileLockオブジェクトの獲得に仕様したFileChannelがクローズされるとき、
- Java仮想マシンが終了したとき
↑のコードではtry-with-resourceを採用しているため
releaseメソッドを使わずとも
FileChannelがAutoClosableの対象となり、勝手にロック解除されます
tryLockメソッドで失敗してOverlappingFileLockExceptionに行ったときは
ロックをしていないために、FileChannelを閉じてそのまま何もせず終了ですね
ファイルがロックされてるかどうかだけ見て、ジョブを起動するにしろファイルロックしたくないみたいな場合には
try-with-resourceの後にジョブを呼ぶか
try句の中でreleaseした後にジョブを呼ぶなど
様々工夫できますね