首先看一下如何简单实现一个java的文件锁
package com.pracbiz.b2bportal.core.eai.backend; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileChannel; import java.nio.channels.FileLock; public class TestLock { public static void main(String[] args) throws IOException, InterruptedException { RandomAccessFile input = null; FileChannel channel = null; FileLock lock = null; final File source = new File("/Users/youwenwu/Downloads/a.txt"); try { if (source.exists()) { input = new RandomAccessFile(source, "rw"); //step 1.获取FileChannel,获取channel有3种方式,分别是调用 //FileInputStream,FileOutputStream以及RandomAccessFile //实例的getChannel方法. channel = input.getChannel(); //step 2.获取FileLock lock = channel.tryLock(); } } finally { if (lock != null) { lock.release(); lock = null; } if (channel != null) { channel.close(); channel = null; } if (input != null) { input.close(); input = null; } } } }
FileLock是通过调用FileChannel的lock或tryLock方法来获取的,lock获取不到当前线程会处于阻塞等待状态,tryLock不管是否成功获得锁都会立即返回。
FileLock分两种,共享锁(shared)和排它锁(exclusive),多个线程可以同时持有同一个文件的共享锁,但但如果有一个线程持有了一个文件的排它锁,则其它线程将无法获取该文件的锁(包括共享锁和排它锁),这里有一点要特别留意,并不是所有的操作系统都支持共享锁,如果某个操作系统不支持共享锁,那么FileChannel的lock或tryLock会自动返回排它锁,如何判断一个FileLock为共享锁还是排它锁,只需要调用lock.isShared方法即可,返回true则为共享锁,false为排它锁。
那接下来回到我们之前的问题,为什么Linux环境下,Thread1持有File1的lock,但是Thread2却能删除File1,我们知道,对于结构型数据库,如果一个存储过程对某一张表中的某一条记录加锁,那另一个存储过程是无法删除该条记录的,这是因为删除一条记录需要获取该记录的独占锁,但是一条记录的独占锁同一时刻只能由一个存储过程获取,所以很显然,这里是删除失败(关于数据库的锁目前就点到为止,不是我们这篇文章需要讨论的范围),那么这里很容易让我们想到,难道Thread2在删除File1的时候不需要获取该文件的锁?这样不是非常不安全吗?带着这个疑问,我查了java api发现,原来锁在操作系统级别有两个特征,协同(advisory)和强制(mandatory),这个是平台相关的,windows平台的锁是mandatory的,linux平台的锁则是advisory的,关于这两种特征的行为,大致可以总结为以下两点,advisory特征的锁并不会阻止其它线程对该文件的访问甚至删除,但mandatory特征的锁会完全阻止其它线程对文件的任何违反锁规则的访问(比如read并不会违反锁规则,但update或者delete却是违反了锁规则),而且java api也明确的告诉我们不要希望lock来帮你阻止其它程序对文件的访问。
该贴持续更新,会加入更多实验数据