write.lock加锁的实现原理:
实现源码(lucene 6.0):
@Override
protected Lock obtainFSLock(FSDirectory dir, String lockName) throws IOException {Path lockDir = dir.getDirectory();// Ensure that lockDir exists and is a directory.// note: this will fail if lockDir is a symlinkFiles.createDirectories(lockDir);Path lockFile = lockDir.resolve(lockName);try {Files.createFile(lockFile);} catch (IOException ignore) {// we must create the file to have a truly canonical path.// if it's already created, we don't care. if it cant be created, it will fail below.}// fails if the lock file does not existfinal Path realPath = lockFile.toRealPath();// used as a best-effort check, to see if the underlying file has changedfinal FileTime creationTime = Files.readAttributes(realPath, BasicFileAttributes.class).creationTime();if (LOCK_HELD.add(realPath.toString())) {FileChannel channel = null;FileLock lock = null;try {channel = FileChannel.open(realPath, StandardOpenOption.CREATE, StandardOpenOption.WRITE);lock = channel.tryLock();if (lock != null) {return new NativeFSLock(lock, channel, realPath, creationTime);} else {throw new LockObtainFailedException("Lock held by another program: " + realPath);}} finally {if (lock == null) { // not successful - clear up and move outIOUtils.closeWhileHandlingException(channel); // TODO: addSuppressedclearLockHeld(realPath); // clear LOCK_HELD last }}} else {throw new LockObtainFailedException("Lock held by this virtual machine: " + realPath);}
}
首先会递归的创建多层目录,然后创建write.lock文件,如果已经存在抛出java.nio.file.FileAlreadyExistsException异常,捕获该异常之后不做任何处理,因为一旦索引创建之后,write.lock文件会一直存在,即使IndexWriter已经关闭,所以不能以该文件是否存在判断是否有多个IndexWriter被打开了,那么是根据什么来判断的呢?
接着会获取该文件创建时候的时间戳,并且将该文件的真实路径加入LOCK_HED, LOCK_HELD的声明如下,是一个同步的HashSet集合,在第一次打开IndexWriter的时候LOCK_HELD.add(realPath.toString())成功,然后调用FIleChannel的API去h获取锁,注意这里面重点就是channel.trylock()获取到的是FIleLock,这是系统级别的锁,即使其它进程想打开IndexWriter的时候,它虽然能够LOCK_HELD.add(realPath.toString())成功,但是在channel.tryLock()步会加锁失败,得到null,这时候程序依然会抛LockObtainFailedException异常;对于多JVM去同时写同一份索引的情况同样如此,如果在同一个进程内,后面希望打开另外的IndexWriter的时候必然LOCK_HELD.add(realPath.toString())失败,抛LockObtainFailedException异常所以如果程序或JVM崩溃,LOCK_HELD在内存中必然也失效,系统级别的锁(使用tryLock获取的文件锁)也会释放,相当于是自动解锁了,不影响下次的重新加锁操作。
补充:FileChannel中的lock()与tryLock()方法都是尝试去获取在某一文件上的独有锁(以下简称独有锁),可以实现进程间操作的互斥。区别在于lock()会阻塞(blocking)方法的执行,tryLock()则不会。
如果进程在执行lock()或tryLock()后获取到独有锁(return a FileLock object),那么进程会一直持有该锁到被释放(文件流被关闭 或 调用release() )。
如果进程P(A)持有独有锁:
1、进程P(B)执行lock()获取独有锁,则lock()所在方法会一直阻塞,直到独有锁被进程P(A)释放。
2、进程P(B)执行tryLock()获取独有锁,则tryLock()会抛出异常java.io.IOException: fcntl failed: EAGAIN (Try again)异常
释放锁源码:
@Override
public void close() throws IOException {if (config.getCommitOnClose()) {shutdown();} else {rollback();}
在shutdown中,Lucene会先判断一系列预先设置的参数,然后进行刷新操作,将所有在内存中缓存的更新刷新到Directory中,然后静静等待合并结束,合并之后会进行内部的提交操作
转载:
lucene源码解析:http://codepub.cn/2016/11/23/Lucene-index-file-lock-principle/
FIleChannel介绍:https://blog.csdn.net/leunging/article/details/73911927