2016-09-02 55 views
6

我注意到java.iojava.nio隨機訪問文件的實現與處理FileLocks的方式略有不同。隨機訪問文件FileLock:java.io vs. java.nio

看起來好像(在Windows上)java.io爲您提供強制性文件鎖定,並且java.nio分別向您提供諮詢文件鎖定。強制性文件鎖意味着該鎖適用於所有進程,並且適用於遵循相同鎖定協議的良好行爲進程的建議。

如果我運行以下示例,我可以手動刪除*.nio文件,而*.io文件拒絕刪除。

import java.io.*; 
import java.lang.management.ManagementFactory; 
import java.nio.*; 
import java.nio.channels.*; 
import java.nio.file.*; 

public class NioIoLock { 

    public static void main(String[] args) throws IOException, InterruptedException { 
     String workDir = System.getProperty("user.dir"); 

     FileChannel channelIo, channelNio; 
     FileLock lockIo, lockNio; 

     // use io 
     { 
      String fileName = workDir 
        + File.separator 
        + ManagementFactory.getRuntimeMXBean().getName() 
        + ".io"; 
      File lockFile = new File(fileName); 
      lockFile.deleteOnExit(); 
      RandomAccessFile file = new RandomAccessFile(lockFile, "rw");    

      channelIo = file.getChannel(); 
      lockIo = channelIo.tryLock(); 
      if (lockIo != null) {     
       channelIo.write(ByteBuffer.wrap("foobar".getBytes("UTF-8"))); 
      } 

     } 

     // use nio 
     { 
      Path workDirPath = Paths.get(workDir); 
      Path file = workDirPath.resolve(
        Paths.get(ManagementFactory.getRuntimeMXBean().getName() + ".nio")); 

      // open/create test file 
      channelNio = FileChannel.open(
        file, StandardOpenOption.READ, StandardOpenOption.WRITE, 
        StandardOpenOption.CREATE, StandardOpenOption.DELETE_ON_CLOSE); 

      // lock file 
      lockNio = channelNio.tryLock(); 
      if (lockNio != null) { 
       channelNio.write(ByteBuffer.wrap("foobar".getBytes("UTF-8"))); 
      } 

     } 

     // do not release locks for some time 
     Thread.sleep(10000); 

     // release io lock and channel 
     if (lockIo != null && lockIo.isValid()) { 
      lockIo.release(); 
     } 
     channelIo.close(); 

     // release nio lock and channel 
     if (lockNio != null && lockNio.isValid()) { 
      lockNio.release(); 
     } 
     channelNio.close(); 
    } 

} 

這是有原因嗎?這兩者甚至被認爲是替代品還是它們意味着無限期地共存?

+0

將'SYNC,DSYNC'添加到nio版本會有所作爲嗎?那麼這將是一個性能考慮。 –

+0

加入'SYNC,DSYNC'沒有什麼區別 – starikoff

回答

6

TL; DR:這不是關於鎖,而是關於文件打開的方式。您在io中看到的是Windows 2000之前可以做的最好的Windows操作系統,即使打開文件只能讀取文件時也是如此 - 無法刪除該文件。您在nio中看到的是使用自Windows 2000以後引入的新功能的改進,但如果您願意,仍然可以在nio中使用舊的行爲。決定不將該能力移植到io的功能中。

全文:我刪除了與鎖定有關的所有代碼(見下文)以及寫入文件,它的工作方式與您的代碼完全相同;不過,我還發現,如果您在打開「nio」通道時指定ExtendedOpenOption.NOSHARE_DELETE,那麼當您嘗試刪除這兩個文件中的任何一個文件時的行爲都是相同的(取消註釋並嘗試)。還發現this question,它有一些解釋,不知道它是否會有所幫助。

import java.io.*; 
import java.lang.management.ManagementFactory; 
import java.nio.*; 
import java.nio.channels.*; 
import java.nio.file.*; 

public class NioIoLock { 

    public static void main(String[] args) 
      throws IOException, InterruptedException { 
     String workDir = System.getProperty("user.dir"); 

     FileChannel channelIo, channelNio; 
     FileLock lockIo, lockNio; 

     // use io 
     { 
      String fileName = workDir + File.separator 
       + ManagementFactory.getRuntimeMXBean().getName() + ".io"; 
      File lockFile = new File(fileName); 
      lockFile.deleteOnExit(); 
      RandomAccessFile file = new RandomAccessFile(lockFile, "rw"); 
      channelIo = file.getChannel(); 
     } 

     // use nio 
     { 
      Path workDirPath = Paths.get(workDir); 
      Path file = workDirPath.resolve(Paths 
       .get(ManagementFactory.getRuntimeMXBean().getName() + ".nio")); 

      // open/create test file 
      channelNio = FileChannel.open(file, StandardOpenOption.READ, 
       StandardOpenOption.WRITE, StandardOpenOption.CREATE, 
       StandardOpenOption.DELETE_ON_CLOSE 
       /*, com.sun.nio.file.ExtendedOpenOption.NOSHARE_DELETE*/); 
     } 

     // do not release locks for some time 
     Thread.sleep(10000); 
    } 
} 

更新1:其實,我仍然不知道這背後的理由,但機制是明確的:

FD 
winFileHandleOpen(JNIEnv *env, jstring path, int flags) 
{ 
    ... 
    const DWORD sharing = 
     FILE_SHARE_READ | FILE_SHARE_WRITE; 
    ... // "sharing" not updated anymore 
    h = CreateFileW(
     pathbuf,   /* Wide char path name */ 
     access,    /* Read and/or write permission */ 
     sharing,   /* File sharing flags */ 
     NULL,    /* Security attributes */ 
     disposition,  /* creation disposition */ 
     flagsAndAttributes, /* flags and attributes */ 
     NULL); 
    ... 
} 
RandomAccessFile構造最終從 method winFileHandleOpen從io_util_md.c調用下面的本機代碼

因此,FILE_SHARE_DELETE標誌未設置,您無法設置它。在另一方面,呼籲java.nio.channels.FileChannel.open(Path, OpenOption...)eventually藉此標誌到:

private static FileDescriptor open(String pathForWindows, 
             String pathToCheck, 
             Flags flags, 
             long pSecurityDescriptor) 
     throws WindowsException 
    { 
     ... 
     int dwShareMode = 0; 
     if (flags.shareRead) 
      dwShareMode |= FILE_SHARE_READ; 
     if (flags.shareWrite) 
      dwShareMode |= FILE_SHARE_WRITE; 
     if (flags.shareDelete) 
      dwShareMode |= FILE_SHARE_DELETE; 
     ... 
     // open file 
     long handle = CreateFile(pathForWindows, 
           dwDesiredAccess, 
           dwShareMode, 
           pSecurityDescriptor, 
           dwCreationDisposition, 
           dwFlagsAndAttributes); 
     ... 
    } 

更新2:JDK-6607535

的建議更改共享模式是RFE 6357433.這將是巨大的要做到這一點,但它可能會破壞現有的應用程序(因爲當前行爲自jdk1.0以來一直存在)。爲RandomAccessFile添加特殊模式以解決Windows問題可能不是正確的方法。此外,還有一些建議,這可以幫助刪除具有文件映射的文件的問題。事實並非如此,因爲文件映射仍然會阻止文件被刪除。無論如何,對於jdk7我們正在研究一個新的文件系統API,它將解決這裏的一些要求。 Windows實現正確,因此可以刪除打開的文件。

另見JDK-6357433從那裏引用:

一位顧客指出的,使用一個FileInputStream上的Windows打開一個文件在java.net論壇將導致其他進程無法刪除該文件。這是代碼當前寫入時的預期行爲,因爲在打開文件時指定的共享標誌是FILE_SHARE_READ和FILE_SHARE_WRITE。請參閱io_util_md.c,fileOpen。但是,Windows 2000提供了一個新標誌FILE_SHARE_DELETE,它允許其他進程在文件仍處於打開狀態時刪除該文件。

最後

解決

對於JDK7的解決方法是使用新的文件系統API。因此,使用f.toPath(),newInputStream()和f.toPath()。newOutputStream()來代替新的FileInputStream(f)和新的FileOutputStream(f)。

+1

很棒的分析。我從來沒有考慮過不會造成這種情況的鎖 – predi