共享內存在Java中的實現和應用

共享內存對應應用開發的意義

對熟知UNIX系統應用開發的程序員來講,IPC(InterProcess Communication)機制是 很是熟悉的,IPC基本包括共享內存、信號燈操做、消息隊列、信號處理等部分,是開發應 用中很是重要的必不可少的工具。其中共享內存IPC機制的關鍵,對於數據共享、系統快 速查詢、動態配置、減小資源耗費等均有獨到的優勢。java

對應UNIX系統來講,共享內存分爲通常共享內存和映像文件共享內存兩種,而對應 Windows,實際上只有映像文件共享內存一種。因此java應用中也是隻能建立映像文件共享 內存。程序員

在java語言中,基本上沒有說起共享內存這個概念,可是,在某一些應用中,共享內 存確實很是有用,例如採用java語言的分佈式應用系統中,存在着大量的分佈式共享對象, 不少時候須要查詢這些對象的狀態,以查看系統是否運行正常或者瞭解這些對象的目前的一 些統計數據和狀態。若是採用網絡通訊的方式,顯然會增長應用的額外負擔,也增長了一些 沒必要要的應用編程。而若是採用共享內存的方式,則能夠直接經過共享內存查看對象的狀態 數據和統計數據,從而減小了一些沒必要要的麻煩。編程

共享內存的使用有以下幾個特色:安全

  1. 能夠被多個進程打開訪問;服務器

  2. 讀寫操做的進程在執行讀寫操做時其餘進程不能進行寫操做;網絡

  3. 多個進程能夠交替對某一共享內存執行寫操做;數據結構

  4. 一個進程執行了內存的寫操做後,不影響其餘進程對該內存的訪問。同時其餘進程對更新後的內存具備可見性。多線程

  5. 在進程執行寫操做時若是異常退出,對其餘進程寫操做禁止應自動解除。app

  6. 相對共享文件,數據訪問的方便性和效率有dom

另外,共享內存的使用上有以下狀況:

  1. 獨佔的寫操做,相應有獨佔的寫操做等待隊列。獨佔的寫操做自己不會發生數據的一致性問題。

  2. 共享的寫操做,相應有共享的寫操做等待隊列。共享的寫操做則要注意防止發生數據的一致性問題。

  3. 獨佔的讀操做,相應有共享的讀操做等待隊列;

  4. 共享的讀操做,相應有共享的讀操做等待隊列。

通常狀況下,咱們只是關心第一二種狀況。

共享內存在java中的實現

在jdk1.4中提供的類MappedByteBuffer爲咱們實現共享內存提供了較好的方法。該緩 衝區其實是一個磁盤文件的內存映像。兩者的變化將保持同步,即內存數據發生變化會立 刻反映到磁盤文件中,這樣會有效的保證共享內存的實現。

將共享內存和磁盤文件創建聯繫的是文件通道類:FileChannel。該類的加入是JDK爲 了統一對外部設備(文件、網絡接口等)的訪問方法,而且增強了多線程對同一文件進行存 取的安全性。例如讀寫操做統一成read和write。這裏只是用它來創建共享內存用,它創建 了共享內存和磁盤文件之間的一個通道。

打開一個文件創建一個文件通道能夠用RandomAccessFile類中的方法getChannel。該 方法將直接返回一個文件通道。該文件通道因爲對應的文件設爲隨機存取文件,一方面能夠 進行讀寫兩種操做,另外一方面使用它不會破壞映像文件的內容(若是用FileOutputStream直 接打開一個映像文件會將該文件的大小置爲0,固然數據會所有丟失)。這裏,若是用 FileOutputStream和FileInputStream則不能理想的實現共享內存的要求,由於這兩個類同時 實現自由的讀寫操做要困可貴多。

下面的代碼實現瞭如上功能,它的做用相似UNIX系統中的mmap函數。

// 得到一個只讀的隨機存取文件對象 
RandomAccessFile RAFile = new RandomAccessFile(filename,"r");

// 得到相應的文件通道 
FileChannel fc = RAFile.getChannel();

// 取得文件的實際大小,以便映像到共享內存 
int size = (int)fc.size();

// 得到共享內存緩衝區,該共享內存只讀 
MappedByteBuffer mapBuf = fc.map(FileChannel.MAP_RO,0,size);

// 得到一個可讀寫的隨機存取文件對象 
RAFile = new RandomAccessFile(filename,"rw");

// 得到相應的文件通道 
fc = RAFile.getChannel();

// 取得文件的實際大小,以便映像到共享內存 
size = (int)fc.size();

// 得到共享內存緩衝區,該共享內存可讀寫 
mapBuf = fc.map(FileChannel.MAP_RW,0,size);

// 獲取頭部消息:存取權限 
mode = mapBuf.getInt();

若是多個應用映像同一文件名的共享內存,則意味着這多個應用共享了同一內存數據。 這些應用對於文件能夠具備同等存取權限,一個應用對數據的刷新會更新到多個應用中。

爲了防止多個應用同時對共享內存進行寫操做,能夠在該共享內存的頭部信息加入寫操 做標誌。該共享內存的頭部基本信息至少有:

   int Length; // 共享內存的長度。
    int mode;   // 該共享內存目前的存取模式。

共享內存的頭部信息是類的私有信息,在多個應用能夠對同一共享內存執行寫操做時, 開始執行寫操做和結束寫操做時,需調用以下方法:

   public boolean StartWrite()
    {
        if(mode == 0) { //  標誌爲0,則表示可寫
            mode = 1; // 置標誌爲1,意味着別的應用不可寫該共享內存
            mapBuf.flip(); 
            mapBuf.putInt(mode); // 寫如共享內存的頭部信息
            return true;
        }
        else {
            return  false; // 指明已經有應用在寫該共享內存,本應用不可寫該共享內存
        }
    }
    
    public boolean StopWrite()
    {
        mode = 0; // 釋放寫權限
        mapBuf.flip(); 
        mapBuf.putInt(mode); // 寫入共享內存頭部信息
        return true;
    }

這裏提供的類文件mmap.java封裝了共享內存的基本接口,讀者能夠用該類擴展成本身須要的功能全面的類。

若是執行寫操做的應用異常停止,那麼映像文件的共享內存將再也不能執行寫操做。爲了 在應用異常停止後,寫操做禁止標誌自動消除,必須讓運行的應用獲知退出的應用。在多線 程應用中,能夠用同步方法得到這樣的效果,可是在多進程中,同步是不起做用的。方法可 以採用的多種技巧,這裏只是描述一可能的實現:採用文件鎖的方式。寫共享內存應用在獲 得對一個共享內存寫權限的時候,除了判斷頭部信息的寫權限標誌外,還要判斷一個臨時的 鎖文件是否能夠獲得,若是能夠獲得,則即便頭部信息的寫權限標誌爲1(上述),也能夠 啓動寫權限,其實這已經代表寫權限得到的應用已經異常退出,這段代碼以下:

// 打開一個臨時的文件,注意同一共享內存,該文件名要相同,能夠在共享文件名後加後綴「.lock」。
RandomAccessFile fis = new RandomAccessFile("shm.lock","rw");
// 得到文件通道
FileChannel lockfc = fis.getChannel();
// 得到文件的獨佔鎖,該方法不產生堵塞,馬上返回
FileLock flock = lockfc.tryLock();
// 若是爲空,則代表已經有應用佔有該鎖
if(flock == null) {
 ...// 不能執行寫操做
}
else {
...// 能夠執行寫操做
}

該鎖會在應用異常退出後自動釋放,這正是該處所須要的方法。

共享內存在java中的應用

共享內存在java應用中,常常有以下兩種種應用:

  1. 永久對象配置。

    在java服務器應用中,用戶可能會在運行過程當中配置一些參數,而這些參數須要永久 有效,當服務器應用從新啓動後,這些配置參數仍然能夠對應用起做用。這就能夠用到該文 中的共享內存。該共享內存中保存了服務器的運行參數和一些對象運行特性。能夠在應用啓 動時讀入以啓用之前配置的參數。

  2. 查詢共享數據。

    一個應用(例 sys.java)是系統的服務進程,其系統的運行狀態記錄在共享內存中,其 中運行狀態多是不斷變化的。爲了隨時瞭解系統的運行狀態,啓動另外一個應用(例 mon.java),該應用查詢該共享內存,彙報系統的運行狀態。

可見,共享內存在java應用中仍是頗有用的,只要組織好共享內存的數據結構,共享內存就能夠在應用開發中發揮很不錯的做用。

相關文章
相關標籤/搜索