在lucene中,为了防止并发操作引起索引文件的破坏,因此引入了锁的机制,其原理跟操作系统中的互斥锁是一致的。那么,lucene是如何实现锁机制的呢?
我们可以看到lucene.Net中有Lucene.Net.Store.Lock这样一个类,它是一个抽象,定义了lucene中锁需要实现的一些基本操作。
2 {
3 public abstract bool Obtain();
4
5 public abstract void Release();
6
7 public abstract bool IsLocked();
8 }
上面可以看到Lock类中包含三个操作,三个函数的功能是分别是锁定资源、释放资源和判断资源是否已被锁定(lucene中需要锁定的资源一般是指某个文件或某个目录)。下面一段代码是Lucene.Net2.0中,Lucene.Net.Store.FSDirectory.AnonymousClassLock对lock的一种实现。
2 {
3 override public bool IsLocked()
4 {
5 if (Lucene.Net.Store.FSDirectory.disableLocks)
6 return false;
7 bool tmpBool;
8 if (System.IO.File.Exists(lockFile.FullName))
9 tmpBool = true;
10 else
11 tmpBool = System.IO.Directory.Exists(lockFile.FullName);
12 return tmpBool;
13 }
14
15 public override bool Obtain()
16 {
17 if (Lucene.Net.Store.FSDirectory.disableLocks)
18 return true;
19
20 bool tmpBool;
21 if (System.IO.File.Exists(Enclosing_Instance.lockDir.FullName))
22 tmpBool = true;
23 else
24 tmpBool = System.IO.Directory.Exists(Enclosing_Instance.lockDir.FullName);
25 if (!tmpBool)
26 {
27 try
28 {
29 System.IO.Directory.CreateDirectory(Enclosing_Instance.lockDir.FullName);
30 }
31 catch (Exception)
32 {
33 throw new System.IO.IOException("Cannot create lock directory: " + Enclosing_Instance.lockDir);
34 }
35 }
36
37 try
38 {
39 System.IO.FileStream createdFile = lockFile.CreateNewFile();
40 createdFile.Close();
41 return true;
42 }
43 catch (Exception)
44 {
45 return false;
46 }
47 }
48
49 public override void Release()
50 {
51 if (Lucene.Net.Store.FSDirectory.disableLocks)
52 return;
53 bool tmpBool;
54 if (System.IO.File.Exists(lockFile.FullName))
55 {
56 System.IO.File.Delete(lockFile.FullName);
57 tmpBool = true;
58 }
59 else if (System.IO.Directory.Exists(lockFile.FullName))
60 {
61 System.IO.Directory.Delete(lockFile.FullName);
62 tmpBool = true;
63 }
64 else
65 tmpBool = false;
66 bool generatedAux = tmpBool;
67 }
68 }
69
上面的代码中,Enclosing_Instance.lockDir.FullName是需要锁定的文件或者目录的完整路径,而lockFile则是需要创建的锁文件,文件后缀名是".lock"。"AnonymousClassLock中的Obtain方法实际上就是创建lockFile文件,Release就是删除lockFile文件。那么Obtain只是创建一个lockFile文件,为什么就实现了加锁的功能呢?
我们来看第39行的代码:System.IO.FileStream createdFile = lockFile.CreateNewFile();
这行代码是被我修改过的,Lucene.Net2.0的原来的调用的函数是lockFile.Create()。CreateNewFile和Create函数的作用都是创建一个文件,为什么要改成CreateNewFile呢?这两个函数有一个很重要的区别,CreateNewFile执行时,如果需要创建的文件已存在,就会引发异常,而Create函数在文件已存在是仍然能正常执行。明白两个函数间的区别,就不难理解它的加锁作用了。当一个对象获取某个资源时,给该资源加锁创建了lockFile文件,另一对象试图获取该对象的访问权时,由于lockFile已存在,调用CreateNewFile就会引发异常,直至另一个对象调用Release函数删除lockFile文件。(CreateNewFile这个函数实际上在.NET中是没有的,而在java中存在,这里这么写是为了方便理解。.NET中,实现CreateNewFile的写法应该是lockFile.Open(System.IO.FileMode.CreateNew),而源码中的Create()写法应该算是个2.0的bug吧,2.4已经修正了)
Lucene.Net2.0中lock的实现是依赖于CreateNewFile函数的互斥性。这样的做法,存在一个问题。当应用程序锁定某一个目录,因为某个异常或者其他原因导致程序异常退出,lockFile没有被删除,程序第二次运行时,则无法获得目录的访问权。下面我们通过一段代码来验证这个问题。
2 {
3 string path = @"d:\test\";
4 Lucene.Net.Analysis.Analyzer analyzer = new Lucene.Net.Analysis.SimpleAnalyzer();
5 Lucene.Net.Index.IndexWriter writer = new Lucene.Net.Index.IndexWriter(path, analyzer, false);
6 throw new Exception();
7 }
第一次运行上面的代码后,在throw new Exception()处抛出异常中止。然后重新运行这段代码,则会看到Lock obtain timed out异常,这是由于第一次异常退出lockFile没有被删除,重新运行后进入死锁。当然,如果是程序自己出现异常是可以通过try...catch...finally代码处理,但是如果出现断电或死机等其他情况呢,那就只有手动去删除lockFile文件。
Lucene通过创建lockFile文件的方式实现了互斥锁,而.NET中同样也有lock,那么能否用.NET中的锁替代lucene的锁呢?答案是否定的。.NET中的锁主要是针对线程安全而设计的,而Lucene中的锁是为了保护索引文件,不仅仅是多线程中的问题,同一线程中也有互斥问题。如果采用.NET的锁机制,那么在同一个线程中将能够创建两个IndexWriter,由于IndexWriter在内存中会缓存一部分document,其中一个IndexWriter发生Merge操作,无法获取到另一IndexWriter缓存的document,则会引起异常,可能导致索引文件被破坏。