Lock/LockFactory类系
综述:Lucene 用“锁”的机制来实现同一文件夹的互斥访问:当有进程访问需要互斥访问的文件夹时,首先查看与之关联的“锁”是否存在,若存在则拒绝访问;若不存在,则先上“锁”,访问之,最后解 “锁”。不同的Lock子类,具体的“锁”实现方式并不一样。
1.Lock/LockFactory类系的层次图
2.部分代码说明
Lock类
Lock本身是一个抽象类,它提供了4个方法,但它仅实现了obtain(long)这一个方法,其他三个留给了它的子类去完成。4个方法的声明罗列如下:
public abstract Boolean obtain() throws IOException;
public boolean obtain(long lockWaitTimeout) throws LockObtainFailedException;;
public abstract void release() throws IOException;
public abstract Boolean isLocked();
Lock还提供了两个静态变量:long LOCK_POLL_INTERVAL(默认值为1000ms)和final long LOCK_OBTAIN_WAIT_FOREVER(值为-1) ,前者为试图获取“锁”时的时间间隔值,后者为当lockWaitTimeout设置为该值时,obtain(long)将会无限期试图获取“锁”。
obtain(long)的功能为在给定的lockWaitTimeout时间内试图获取“锁”,一旦获取到,则返回;超过时间则会抛出异常。其代码及注释如下:
1 public boolean obtain(long lockWaitTimeout)
2 throws LockObtainFailedException, IOException {
3 failureReason = null;
4 // locked试图获取“锁文件”。obtain()的功能是及时返回是否能取得“锁文件”
5 boolean locked = obtain();
6 // 给定参数值为负并且不等于-1,则抛出参数值设置异常
7 if (lockWaitTimeout < 0 && lockWaitTimeout != LOCK_OBTAIN_WAIT_FOREVER)
8 throw new IllegalArgumentException(
9 "lockWaitTimeout should be LOCK_OBTAIN_WAIT_FOREVER or a non-negative number (got "
10 + lockWaitTimeout + ")");
11 // 设置最大睡眠次数
12 long maxSleepCount = lockWaitTimeout / LOCK_POLL_INTERVAL;
13 long sleepCount = 0; // 睡眠次数累加器
14 // 循环直到取得“锁文件”;或者时间到,则抛出异常
15 while (!locked) {
16 if (lockWaitTimeout != LOCK_OBTAIN_WAIT_FOREVER
17 && sleepCount++ >= maxSleepCount) { // 参数lockWaitTimeout不为-1且累计睡眠次数大于maxSleepCount,抛出异常
18 String reason = "Lock obtain timed out: " + this.toString();
19 if (failureReason != null) {
20 reason += ": " + failureReason;
21 }
22 LockObtainFailedException e = new LockObtainFailedException(
23 reason);
24 if (failureReason != null) {
25 e.initCause(failureReason);
26 }
27 throw e;
28 }
29 try {
30 // 睡眠LOCK_POLL_INTERVAL(默认1000ms)时间
31 Thread.sleep(LOCK_POLL_INTERVAL);
32 } catch (InterruptedException e) {
33 throw new IOException(e.toString());
34 }
35 // 再次试图获取“锁文件”
36 locked = obtain();
37 }
38 // 正常退出,
39 return locked;
40 }
SimpleFSLock类
在SimpleFSLock类中,“锁”是通过给需要访问的文件夹另外建立一个文件的方式来实现的;查看某文件夹是否被上锁,你需要做的仅仅是查看下与其相关的“锁文件”是否存在;解锁时只需删除“锁文件”就万事OK了。下面是obtain()方法的代码及注释:
1 public boolean obtain() throws IOException {
2
3 // Ensure that lockDir exists and is a directory:
4 // 确保lockDir存在并且是文件夹类型
5 if (!lockDir.exists()) {
6 if (!lockDir.mkdirs()) // 如果lockDir不存在,则试图为其建立新文件夹,建立失败则抛出异常
7 throw new IOException("Cannot create directory: "
8 + lockDir.getAbsolutePath());
9 } else if (!lockDir.isDirectory()) {
10 // 如果lockDir存在,但不是文件夹,抛出异常
11 throw new IOException(
12 "Found regular file where directory expected: "
13 + lockDir.getAbsolutePath());
14 }
15 // createNewFile成功,返回true; 失败,false;
16 // 说明:建立成功,也就是说“锁文件”不存在; 失败,说明“锁文件”已经存在,也就是说该文件夹已被上锁
17 return lockFile.createNewFile();
18 }
NativeFSLock类
NativeFSLock与SimpleFSLock有些不同,它的“锁”用的是“锁文件”的锁,说起来很是绕口,其实它只是给“锁文件”上一把锁:在查看某文件夹是否能被访问时,首先检查与此文件夹关联的“锁文件”的锁是否被占用,而不像SimpleFSLock仅仅查看与之相连的“锁文件”是否存在。正因为如此,它解决了如果JVM异常退出时遗留的“锁文件”的问题:在SimpleFSLock中,只要“锁文件”存在,就被人为该文件夹被锁,而不能被任何其他进程访问。
NativeFSLock额外定义了一个私有静态变量:private static HashSet LOCK_HELD。它用来记录“锁文件”的标准路径名(canonical path),当某文件夹的“锁文件”标准路径名存在于LOCK_HELD中,且没有被上锁,就说明该文件夹可被访问;否则拒绝访问。在解锁时,需要从LOCK_HELD中删除“锁文件”的标准路径名,删除“锁文件”。
NativeFSLock的代码中包含了很多对于异常的处理,使得程序看起来很是费解。
obtain()的主要代码及注释如下:
1 public synchronized boolean obtain() throws IOException { // 该方法被设置为同步访问
2 // isLocked()为true说明“锁文件”已被上锁,正在被使用中
3 if (isLocked()) {
4 // Our instance is already locked:
5 return false;
6 }
7
8 // Ensure that lockDir exists and is a directory.
9 if (!lockDir.exists()) {
10 if (!lockDir.mkdirs())
11 throw new IOException("Cannot create directory: "
12 + lockDir.getAbsolutePath());
13 } else if (!lockDir.isDirectory()) {
14 throw new IOException(
15 "Found regular file where directory expected: "
16 + lockDir.getAbsolutePath());
17 }
18
19 String canonicalPath = path.getCanonicalPath();
20
21 boolean markedHeld = false; //标记在LOCK_HELD中是否存在某“锁文件”的路径名
22
23 try {
24
25 // Make sure nobody else in-process has this lock held
26 // already, and, mark it held if not:
27
28 synchronized (LOCK_HELD) { // 设置LOCK_HELD的同步访问
29 if (LOCK_HELD.contains(canonicalPath)) { // 如果标准路径存在于LOCK_HELD中,说明该文件被上锁或正在被上锁,返回false
30 // Someone else in this JVM already has the lock:
31 return false;
32 } else {
33 // This "reserves" the fact that we are the one
34 // thread trying to obtain this lock, so we own
35 // the only instance of a channel against this
36 // file:
37 LOCK_HELD.add(canonicalPath); // 添加路径名到LOCK_HELD中
38 markedHeld = true; // 设置为true
39 }
40 }
41
42 try {
43 // 建立“锁文件”
44 f = new RandomAccessFile(path, "rw");
45 } catch (IOException e) {
46 // On Windows, we can get intermittant "Access
47 // Denied" here. So, we treat this as failure to
48 // acquire the lock, but, store the reason in case
49 // there is in fact a real error case.
50 failureReason = e;
51 f = null; // 建立失败,则f= null
52 }
53
54 if (f != null) {
55 try {
56 // 获取“锁文件”的通道
57 channel = f.getChannel();
58 try {
59 // 给“锁文件”上锁
60 lock = channel.tryLock();
61 } catch (IOException e) {
62 // At least on OS X, we will sometimes get an
63 // intermittant "Permission Denied" IOException,
64 // which seems to simply mean "you failed to get
65 // the lock". But other IOExceptions could be
66 // "permanent" (eg, locking is not supported via
67 // the filesystem). So, we record the failure
68 // reason here; the timeout obtain (usually the
69 // one calling us) will use this as "root cause"
70 // if it fails to get the lock.
71 failureReason = e;
72 } finally {
73 if (lock == null) { // 如果没有取得锁,需关闭通道并设置其为null
74 try {
75 channel.close(); //关闭通道
76 } finally {
77 channel = null; //设置为null
78 }
79 }
80 }
81 } finally {
82 if (channel == null) { // 如果通道获取失败或者上锁异常,关闭“锁文件”
83 try {
84 f.close(); // 关闭“锁文件”
85 } finally {
86 f = null;
87 }
88 }
89 }
90 }
91
92 } finally {
93 // markedHeld为ture,但isLocked()为false,说明上锁途中出现异常
94 // 需删除“锁文件”的路径名
95 if (markedHeld && !isLocked()) {
96 synchronized (LOCK_HELD) { // 注意同步访问LOCK_HELD
97 if (LOCK_HELD.contains(canonicalPath)) {
98 LOCK_HELD.remove(canonicalPath);
99 }
100 }
101 }
102 }
103 // 经过以上过程,若成功上锁则isLock()为true;反之,false
104 return isLocked();
105 }