本文整理自《Java NIO》一书。
FileChannel对象是线程安全(thread-safe)的。多个进程可以在同一个实例上并发调用方法而不会引起任何问题,不过并非所有的操作都是多线程的(multithreaded)。影响通道位置或者影响文件大小的操作都是单线程的(single-threaded)。如果有一个线程已经在执行会影响通道位置或文件大小的操作,那么其他尝试进行此类操作之一的线程必须等待。并发行为也会受到底层的操作系统或文件系统影响。
同大多数I/O相关的类一样,FileChannel是一个反映Java虚拟机外部一个具体对象的抽象。FileChannel类保证同一个Java虚拟机上的所有实例看到的某个文件的视图均是一致的,但是Java虚拟机却不能对超出它控制范围的因素提供担保。通过一个FileChannel实例看到的某个文件的视图同通过一个外部的非Java进程看到的该文件的视图可能一致,也可能不一致。多个进程发起的并发文件访问的语义高度取决于底层的操作系统和(或)文件系统。一般而言,由运行在不同Java虚拟机上的FileChannel对象发起的对某个文件的并发访问和由非Java进程发起的对该文件的并发访问是一致的。
下面列出FileChannel、RandomAccessFile和POSIX(可移植操作系统接口) I/O system calls三者在方法上的对应关系:

<
文件空洞(file hole)
在UNIX文件操作中,文件位移量可以大于文件的当前长度,在这种情况下,对该文件的下一次写将延长该文件,并在文件中构成一个空洞,这一点是允许的。位于文件中但没有写过的字节都被设为 0。
如果 offset 比文件的当前长度更大,下一个写操作就会把文件“撑大(extend)”。这就是所谓的在文件里创造“空洞(hole)”。没有被实际写入文件的所有字节由重复的 0 表示。空洞是否占用硬盘空间是由文件系统(file system)决定的。
用ls查看的文件大小是将空洞算在内的。
cp命令拷贝的文件,空洞部分不拷贝,所以生成的同样文件占用磁盘空间小。
用read读取空洞部分读出的数据是0,所以如果用read和write拷贝一个有空洞的文件,那么最终得到的文件没有了空洞,空洞部分都被0给填充了,文件占用的磁盘空间就大了。不过文件大小不变。
空洞文件作用很大,例如迅雷下载文件,在未下载完成时就已经占据了全部文件大小的空间,这时候就是空洞文件。下载时如果没有空洞文件,多线程下载时文件就都只能从一个地方写入,这就不是多线程了。如果有了空洞文件,可以从不同的地址写入,就完成多线程的优势任务。>
类似于缓冲区的get( ) 和put( )方法,FileChannel中,当字节被read( )或write( )方法传输时,文件position会自动更新。如果position值达到了文件大小的值(文件大小的值可以通过size( )方法返回),read( )方法会返回一个文件尾条件值(-1)。可是,不同于缓冲区的是,如果实现write(
)方法时position前进到超过文件大小的值,该文件会扩展以容纳新写入的字节。同样类似于缓冲区,也有带position参数的绝对形式的read( )和write( )方法。这种绝对形式的方法在返回值时不会改变当前的文件position。由于通道的状态无需更新,因此绝对的读和写可能会更加有效率,操作请求可以直接传到本地代码。更妙的是,多个线程可以并发访问同一个文件而不会相互产生干扰。这是因为每次调用都是原子性的(atomic),并不依靠调用之间系统所记住的状态。
如果文件位于一个本地文件系统,那么一旦force( )方法返回,即可保证从通道被创建(或上次调用force( ))时起的对文件所做的全部修改已经被写入到磁盘。对于关键操作如事务(transaction)处理来说,这一点是非常重要的,可以保证数据完整性和可靠的恢复。然而,如果文件位于一个远程的文件系统,如NFS上,那么不能保证待定修改一定能同步到永久存储器(permanent storage)上,因Java虚拟机不能做操作系统或文件系统不能实现的承诺。如果您的程序在面临系统崩溃时必须维持数据完整性,先去验证一下您在使用的操作系统和(或)文件系统在同步修改方面是可以依赖的。
force( )方法的布尔型参数表示在方法返回值前文件的元数据(metadata)是否也要被同步更新到磁盘。元数据指文件所有者、访问权限、最后一次修改时间等信息。大多数情形下,该信息对数据恢复而言是不重要的。给force(
)方法传递false值表示在方法返回前只需要同步文件数据的更改。大多数情形下,同步元数据要求操作系统进行至少一次额外的底层I/O操作。一些大数量事务处理程序可能通过在每次调用force( )方法时不要求元数据更新来获取较高的性能提升,同时也不会牺牲数据完整性。
如果一个线程在某个文件上获得了一个独占锁,然后第二个线程利用一个单独打开的通道来请求该文件的独占锁,那么第二个线程的请求会被批准。但如果这两个线程运行在不同的Java虚拟机上,那么第二个线程会阻塞,因为锁最终是由操作系统或文件系统来判优的并且几乎总是在进程级而非线程级上判优。file lock 锁与文件关联,而不是与通道或文件句柄关联。我们使用锁来判优外部进程,而不是判优同一个Java虚拟机上的线程。如果您在使用完一个锁后而不释放它的话,可能会导致冲突或者死锁。请小心管理文件锁以避免出现此问题。一旦您成功地获取了一个文件锁,如果随后在通道上出现错误的话,请务必释放这个锁。finally{
lock.release() }
FileLock也是线程安全的,多个线程可以并发访问同一个锁对象实例。
以下出自taobao@boyan 《NIO trick and trap》


