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
}