首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 控制應用程式進程實例數:基於檔案鎖的跨平台實作

     編輯:Java 相關知識

問題背景

C/C# 透過 Windows 登錄表或 Mutex 實現單一實例,但 Java 跨平台特性無法依賴 OS 特有機制。需尋找**進程間可共享、跨平台**的資源實現互斥:

  • Socket 埠口:佔用稀缺網路資源,且可能衝突。
  • 檔案鎖:java.nio.channels.FileLock 提供跨平台強制/建議鎖,且可自然擴充至「允許 N 個實例」。

設計思路

  1. 以 **Class 檔案所在目錄** 作為應用程式唯一身分識別 (同一份 Class/JAR 啟動的進程視為同一應用)。
  2. 在該目錄下建立 InstTmp 子目錄,每個進程啟動時建立唯一暫存檔並嘗試加鎖。
  3. 掃描目錄下**已成功加鎖**的暫存檔數量 = 目前運行實例數。
  4. 進程結束時釋放鎖並刪除暫存檔 (含 deleteOnExit() 雙重保障)。

核心類別:InstanceCount

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(); }
    }
}

輔助類別:Path (取得 Class 絕對路徑)

詳見 獲取類的 class 檔案絕對路徑 一文。核心邏輯:透過 ProtectionDomain → CodeSource → URL 解析,支援 JAR 內類別與檔案系統類別。

使用範例:限制最多 3 個實例

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();
    }
}

注意事項與限制

  • 檔案系統需支援強制鎖:Windows/Linux 預設支援;NFS 等網路檔案系統可能僅支援建議鎖,需測試驗證。
  • 權限:應用程式需對 Class 目錄擁有寫入權限。
  • 殘留檔案清理:deleteOnExit() 僅在正常 JVM 關閉生效;scanLockedFiles() 會自動清理孤立檔案 (無鎖或鎖無效)。
  • 應用程式身分定義:本實作以「同一份 Class/JAR 檔案」為界;若將 JAR 複製到不同目錄執行,視為不同應用。可依需求調整 Path.getPathFromClass() 邏輯 (如改用 JAR 檔案路徑)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved