C/C# 透過 Windows 登錄表或 Mutex 實現單一實例,但 Java 跨平台特性無法依賴 OS 特有機制。需尋找**進程間可共享、跨平台**的資源實現互斥:
java.nio.channels.FileLock 提供跨平台強制/建議鎖,且可自然擴充至「允許 N 個實例」。InstTmp 子目錄,每個進程啟動時建立唯一暫存檔並嘗試加鎖。deleteOnExit() 雙重保障)。package mytools;
import java.io.*;
import java.nio.channels.*;
public class InstanceCount {
private int count = 0;
private static InstanceCount theInstance = null;
private File instDir;
private File myTempFile;
private FileChannel channel;
private FileLock lock;
private RandomAccessFile raf;
private InstanceCount() throws IOException {
// 1. 定位應用程式 Class 目錄
String classPath = Path.getPathFromClass(this.getClass());
File classDir = new File(classPath);
instDir = new File(classDir.getParent(), "InstTmp");
if (!instDir.exists()) instDir.mkdirs();
// 2. 建立專屬暫存檔並加鎖
myTempFile = File.createTempFile("~Inst", ".tmp", instDir);
raf = new RandomAccessFile(myTempFile, "rw");
channel = raf.getChannel();
lock = channel.tryLock(); // 非阻塞嘗試加鎖
myTempFile.deleteOnExit(); // JVM 關閉時自動刪除
if (lock != null) {
count++; // 本實例加鎖成功
} else {
throw new IOException("無法取得檔案鎖,可能已達實例上限");
}
}
/** 掃描已加鎖暫存檔數量 = 實例數 */
private void scanLockedFiles() {
File[] lockedFiles = instDir.listFiles((dir, name) -> {
if (!name.endsWith(".tmp")) return false;
File f = new File(dir, name);
try (RandomAccessFile testRaf = new RandomAccessFile(f, "rw")) {
FileLock testLock = testRaf.getChannel().tryLock();
if (testLock == null || !testLock.isValid()) {
// 已被其他進程鎖定 = 存活實例
return true;
} else {
// 孤立檔案 (進程異常結束未清理),釋放並刪除
testLock.release();
f.delete();
return false;
}
} catch (IOException e) {
return false;
}
});
count = (lockedFiles == null) ? 0 : lockedFiles.length;
}
public static InstanceCount getInstance() {
if (theInstance == null) {
try {
theInstance = new InstanceCount();
} catch (IOException e) {
return null;
}
}
return theInstance;
}
public int getCount() {
scanLockedFiles();
return count;
}
protected void finalize() {
try {
if (lock != null) lock.release();
if (channel != null) channel.close();
if (raf != null) raf.close();
if (myTempFile != null) myTempFile.delete();
// 注意:不刪除 instDir,避免影響其他實例
} catch (IOException ignored) {}
}
// 測試主程式
public static void main(String[] args) {
try {
InstanceCount ic = InstanceCount.getInstance();
System.out.println("目前實例數: " + ic.getCount());
Thread.sleep(5000); // 保持運行供驗證
} catch (Exception e) { e.printStackTrace(); }
}
}
詳見 獲取類的 class 檔案絕對路徑 一文。核心邏輯:透過 ProtectionDomain → CodeSource → URL 解析,支援 JAR 內類別與檔案系統類別。
public class MyApp {
public static void main(String[] args) {
InstanceCount ic = InstanceCount.getInstance();
if (ic == null || ic.getCount() > 3) {
System.err.println("已達最大實例數 (3),啟動中止。");
System.exit(1);
}
// 正常啟動應用程式...
new MyApp().run();
}
}
deleteOnExit() 僅在正常 JVM 關閉生效;scanLockedFiles() 會自動清理孤立檔案 (無鎖或鎖無效)。Path.getPathFromClass() 邏輯 (如改用 JAR 檔案路徑)。