1 共享內存對應應用開發的意義
對熟知UNIX系統應用開發的程序員來講,IPC(InterProcess Communication)機制是很是熟悉的,IPC基本包括共享內存、信號燈操做、消息隊列、信號處理等部分,是開發應用中很是重要的必不可少的工具。其中共享內存IPC機制的關鍵,對於數據共享、系統快速查詢、動態配置、減小資源耗費等均有獨到的優勢。
對應UNIX系統來講,共享內存分爲通常共享內存和映像文件共享內存兩種,而對應 Windows,實際上只有映像文件共享內存一種。因此java應用中也是隻能建立映像文件共享內存。
在java語言中,基本上沒有說起共享內存這個概念,可是,在某一些應用中,共享內存確實很是有用,例如採用java語言的分佈式應用系統中,存在着大量的分佈式共享對象,不少時候須要查詢這些對象的狀態,以查看系統是否運行正常或者瞭解這些對象的目前的一些統計數據和狀態。若是採用網絡通訊的方式,顯然會增長應用的額外負擔,也增長了一些沒必要要的應用編程。而若是採用共享內存的方式,則能夠直接經過共享內存查看對象的狀態數據和統計數據,從而減小了一些沒必要要的麻煩。
共享內存的使用有以下幾個特色:
能夠被多個進程打開訪問;
讀寫操做的進程在執行讀寫操做時其餘進程不能進行寫操做;
多個進程能夠交替對某一共享內存執行寫操做;
一個進程執行了內存的寫操做後,不影響其餘進程對該內存的訪問。同時其餘進程對更新後的內存具備可見性。
在進程執行寫操做時若是異常退出,對其餘進程寫操做禁止應自動解除。
相對共享文件,數據訪問的方便性和效率有
另外,共享內存的使用上有以下狀況:
獨佔的寫操做,相應有獨佔的寫操做等待隊列。獨佔的寫操做自己不會發生數據的一致性問題。
共享的寫操做,相應有共享的寫操做等待隊列。共享的寫操做則要注意防止發生數據的一致性問題。
獨佔的讀操做,相應有共享的讀操做等待隊列;
共享的讀操做,相應有共享的讀操做等待隊列。
通常狀況下,咱們只是關心第一二種狀況。
2 共享內存在java中的實現
在jdk1.4中提供的類MappedByteBuffer爲咱們實現共享內存提供了較好的方法。該緩衝區其實是一個磁盤文件的內存映像。兩者的變化將保持同步,即內存數據發生變化會馬上反映到磁盤文件中,這樣會有效的保證共享內存的實現。
將共享內存和磁盤文件創建聯繫的是文件通道類:FileChannel。該類的加入是JDK爲了統一對外部設備(文件、網絡接口等)的訪問方法,而且增強了多線程對同一文件進行存取的安全性。例如讀寫操做統一成read和write。這裏只是用它來創建共享內存用,它創建了共享內存和磁盤文件之間的一個通道。
打開一個文件創建一個文件通道能夠用RandomAccessFile類中的方法getChannel。該方法將直接返回一個文件通道。該文件通道因爲對應的文件設爲隨機存取文件,一方面能夠進行讀寫兩種操做,另外一方面使用它不會破壞映像文件的內容(若是用FileOutputStream直接打開一個映像文件會將該文件的大小置爲0,固然數據會所有丟失)。這裏,若是用 FileOutputStream和FileInputStream則不能理想的實現共享內存的要求,由於這兩個類同時實現自由的讀寫操做要困可貴多。
下面的代碼實現瞭如上功能,它的做用相似UNIX系統中的mmap函數。
java
// 得到一個只讀的隨機存取文件對象 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服務器應用中,用戶可能會在運行過程當中配置一些參數,而這些參數須要永久有效,當服務器應用從新啓動後,這些配置參數仍然能夠對應用起做用。這就能夠用到該文中的共享內存。該共享內存中保存了服務器的運行參數和一些對象運行特性。能夠在應用啓動時讀入以啓用之前配置的參數。
查詢共享數據。
一個應用(例 sys.java)是系統的服務進程,其系統的運行狀態記錄在共享內存中,其中運行狀態多是不斷變化的。爲了隨時瞭解系統的運行狀態,啓動另外一個應用(例 mon.java),該應用查詢該共享內存,彙報系統的運行狀態。
可見,共享內存在java應用中仍是頗有用的,只要組織好共享內存的數據結構,共享內存就能夠在應用開發中發揮很不錯的做用。服務器
能夠實際測試一下下面的程式處理: 網絡
public QuickFind() { ..... ..... FileChannel fch = new RandomAccessFile(path, "r").getChannel(); ByteBuffer roBuf = fch.map(FileChannel.MapMode.READ_ONLY, 0, (int)fch.size()); DataInputStream is = new DataInputStream(newInputStream(roBuf)); String aa = ""; while(true){ aa = is.readLine(); ..... ..... } public static InputStream newInputStream(final ByteBuffer buf) { return new InputStream() { public synchronized int read() throws IOException { if (!buf.hasRemaining()) { return -1; } return buf.get(); } public synchronized int read(byte[] bytes, int off, int len) throws IOException { // Read only what's left len = Math.min(len, buf.remaining()); buf.get(bytes, off, len); return len; } }; }
速度應該能夠提高許多數據結構