Java 共享內存
共享內存可以說是最有用的進程間通信方式,也是最快的IPC(Inter-Process Communication)形式。兩個不同進程A、B共享內存的意思是,同一塊物理內存被映射到進程A、B各自的進程地址 空間。進程A可以即時看到進程B對共享內存中數據的更新,反之亦然。由于多個進程共享同一塊內存區域,必然需要某種同步機制,互斥鎖和信號量都可以。采用共享內存通信的一個顯而易見的好處是效率高,因為進程可以直接讀寫內存,而不需要任何數據的拷貝。對于像管道和消息隊列等通信方式,則需要在內核和用 戶空間進行四次的數據拷貝,而共享內存則只拷貝兩次數據:一次從輸入文件到共享內存區,另一次從共享內存區到輸出文件。實際上,進程之間在共享內存時,并 不總是讀寫少量數據后就解除映射,有新的通信時,再重新建立共享內存區域。而是保持共享區域,直到通信完畢為止,這樣,數據內容一直保存在共享內存中,并 沒有寫回文件。共享內存中的內容往往是在解除映射時才寫回文件的。因此,采用共享內存的通信方式效率是非常高的。
共享內存的使用有如下幾個特點:
- 可以被多個進程打開訪問;
- 讀寫操作的進程在執行讀寫操作時其他進程不能進行寫操作;
- 多個進程可以交替對某一共享內存執行寫操作;
- 一個進程執行了內存的寫操作后,不影響其他進程對該內存的訪問。同時其他進程對更新后的內存具有可見性。
- 在進程執行寫操作時如果異常退出,對其他進程寫操作禁止應自動解除。
- 相對共享文件,數據訪問的更方便更效率
另外,共享內存的使用上有如下情況:
獨占的寫操作,相應有獨占的寫操作等待隊列。獨占的寫操作本身不會發生數據的一致性問題。
共享的寫操作,相應有共享的寫操作等待隊列。共享的寫操作則要注意防止發生數據的一致性問題。
獨占的讀操作,相應有共享的讀操作等待隊列;
共享的讀操作,相應有共享的讀操作等待隊列。
共享內存在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 {
...// 可以執行寫操作
}
該鎖會在應用異常退出后自動釋放,這正是該處所需要的方法。
3 共享內存在java中的應用
共享內存在java應用中,經常有如下兩種種應用:
永久對象配置。
在 java服務器應用中,用戶可能會在運行過程中配置一些參數,而這些參數需要永久 有效,當服務器應用重新啟動后,這些配置參數仍然可以對應用起作用。這就可以用到該文 中的共享內存。該共享內存中保存了服務器的運行參數和一些對象運行特性。可以在應用啟 動時讀入以啟用以前配置的參數。
查詢共享數據。
一個應用是系統的服務進程,其系統的運行狀態記錄在共享內存中,其中運行狀態可能是不斷變化的。為了隨時了解系統的運行狀態,啟動另一個應用,該應用查詢該共享內存,匯報系統的運行狀態。
可見,共享內存在java應用中還是很有用的,只要組織好共享內存的數據結構,共享內存就可以在應用開發中發揮很不錯的作用。
posted on 2008-04-11 11:22 gembin 閱讀(3833) 評論(1) 編輯 收藏 所屬分類: JavaSE