一 .前言

某年某月某天,同事说需要一个文件排他锁功能,需求如下:

(1)写操作是排他属性

(2)适用于同一进程的多线程/也适用于多进程的排他操作

(3)容错性:获得锁的进程若Crash,不影响到后续进程的正常获取锁

二 .解决方案

1. 最初的构想

在Java领域,同进程的多线程排他实现还是较简易的。比如使用线程同步变量标示是否已锁状态便可。但不同进程的排他实现就比较繁琐。使用已有API,自然想到 java.nio.channels.FileLock:如下

/**

* @param file

* @param strToWrite

* @param append

* @param lockTime 以毫秒为单位,该值只是方便模拟排他锁时使用,-1表示不考虑该字段

* @return

*/

public static boolean lockAndWrite(File file, String strToWrite, boolean append,int lockTime){

if(!file.exists()){

return false;

}

RandomAccessFile fis = null;

FileChannel fileChannel = null;

FileLock fl = null;

long tsBegin = System.currentTimeMillis();

try {

fis = new RandomAccessFile(file, "rw");

fileChannel = fis.getChannel();

fl = fileChannel.tryLock();

if(fl == null || !fl.isValid()){

return false;

}

log.info("threadId = {} lock success", Thread.currentThread());

// if append

if(append){

long length = fis.length();

fis.seek(length);

fis.writeUTF(strToWrite);

//if not, clear the content , then write

}else{

fis.setLength(0);

fis.writeUTF(strToWrite);

}

long tsEnd = System.currentTimeMillis();

long totalCost = (tsEnd - tsBegin);

if(totalCost < lockTime){

Thread.sleep(lockTime - totalCost);

}

} catch (Exception e) {

log.error("RandomAccessFile error",e);

return false;

}finally{

if(fl != null){

try {

fl.release();

} catch (IOException e) {

e.printStackTrace();

}

}

if(fileChannel != null){

try {

fileChannel.close();

} catch (IOException e) {

e.printStackTrace();

}

}

if(fis != null){

try {

fis.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

return true;

}

一切看起来都是那么美好,似乎无懈可击。于是加上两种测试场景代码:

(1)同一进程,两个线程同时争夺锁,暂定命名为测试程序A,期待结果:有一线程获取锁失败

(2)执行两个进程,也就是执行两个测试程序A,期待结果:有一进程某线程获得锁,另一线程获取锁失败

public static void main(String[] args) {

new Thread("write-thread-1-lock"){

@Override

public void run() {

FileLockUtils.lockAndWrite(new File("/data/hello.txt"), "write-thread-1-lock" + System.currentTimeMillis(), false, 30 * 1000);}

}.start();

new Thread("write-thread-2-lock"){

@Override

public void run() {

FileLockUtils.lockAndWrite(new File("/data/hello.txt"), "write-thread-2-lock" + System.currentTimeMillis(), false, 30 * 1000);

}

}.start();

}

2.世界不像你想的那样

上面的测试代码在单个进程内可以达到我们的期待。但是同时运行两个进程,在Mac环境(java8) 第二个进程也能正常获取到锁,在Win7(java7)第二个进程则不能获取到锁。为什么?难道TryLock不是排他的?

其实不是TryLock不是排他,而是channel.close 的问题,官方说法:

On some systems, closing a channel releases all locks held by the Java virtual machine on the

underlying file regardless of whether the locks were acquired via that channel or via

another channel open on the same file.It is strongly recommended that, within a program, a unique

channel be used to acquire all locks on any given file.

原因就是在某些操作系统,close某个channel将会导致JVM释放所有lock。也就是说明了上面的第二个测试用例为什么会失败,因为第一个进程的第二个线程获取锁失败后,我们调用了channel.close ,所有将会导致释放所有lock,所有第二个进程将成功获取到lock。

在经过一段曲折寻找真理的道路后,终于在stackoverflow上找到一个帖子 ,指明了 lucence 的 NativeFSLock,NativeFSLock 也是存在多个进程排他写的需求。笔者参考的是lucence 4.10.4 的NativeFSLock源码,具体可见地址,具体可见obtain 方法,NativeFSLock 的设计思想如下:

(1)每一个锁,都有本地对应的文件。

(2)本地一个static类型线程安全的Set LOCK_HELD维护目前所有锁的文件路径,避免多线程同时获取锁,多线程获取锁只需判断LOCK_HELD是否已有对应的文件路径,有则表示锁已被获取,否则则表示没被获取。

(3)假设LOCK_HELD 没有对应文件路径,则可对File的channel TryLock。

public synchronized boolean obtain() throws IOException {

if (lock != null) {

// Our instance is already locked:

return false;

}

// Ensure that lockDir exists and is a directory.

if (!lockDir.exists()) {

if (!lockDir.mkdirs())

throw new IOException("Cannot create directory: " + lockDir.getAbsolutePath());

} else if (!lockDir.isDirectory()) {

// TODO: NoSuchDirectoryException instead?

throw new IOException("Found regular file where directory expected: " + lockDir.getAbsolutePath());

}

final String canonicalPath = path.getCanonicalPath();

// Make sure nobody else in-process has this lock held

// already, and, mark it held if not:

// This is a pretty crazy workaround for some documented

// but yet awkward JVM behavior:

//

// On some systems, closing a channel releases all locks held by the

// Java virtual machine on the underlying file

// regardless of whether the locks were acquired via that channel or via

// another channel open on the same file.

// It is strongly recommended that, within a program, a unique channel

// be used to acquire all locks on any given

// file.

//

// This essentially means if we close "A" channel for a given file all

// locks might be released... the odd part

// is that we can't re-obtain the lock in the same JVM but from a

// different process if that happens. Nevertheless

// this is super trappy. See LUCENE-5738

boolean obtained = false;

if (LOCK_HELD.add(canonicalPath)) {

try {

channel = FileChannel.open(path.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE);

try {

lock = channel.tryLock();

obtained = lock != null;

} catch (IOException | OverlappingFileLockException e) {

// At least on OS X, we will sometimes get an

// intermittent "Permission Denied" IOException,

// which seems to simply mean "you failed to get

// the lock". But other IOExceptions could be

// "permanent" (eg, locking is not supported via

// the filesystem). So, we record the failure

// reason here; the timeout obtain (usually the

// one calling us) will use this as "root cause"

// if it fails to get the lock.

failureReason = e;

}

} finally {

if (obtained == false) { // not successful - clear up and move

// out

clearLockHeld(path);

final FileChannel toClose = channel;

channel = null;

closeWhileHandlingException(toClose);

}

}

}

return obtained;

}

总结

以上就是本文关于Java编程实现排他锁代码详解的全部内容,感兴趣的朋友可以参阅:Java并发编程之重入锁与读写锁、详解java中的互斥锁信号量和多线程等待机制、Java语言中cas指令的无锁编程实现实例以及本站其他相关专题,希望对大家有所帮助。如有不足之处,欢迎留言指出,小编一定及时更正,给大家提供更好的阅读环境和帮助,感谢朋友们对本站的支持

java一个方法排他调用_Java编程实现排他锁代码详解相关推荐

  1. java中math的方法_Java中Math类常用方法代码详解

    近期用到四舍五入想到以前整理了一点,就顺便重新整理好经常见到的一些四舍五入,后续遇到常用也会直接在这篇文章更新... public class Demo{ public static void mai ...

  2. java语言链栈_Java语言实现数据结构栈代码详解

    近来复习数据结构,自己动手实现了栈.栈是一种限制插入和删除只能在一个位置上的表.最基本的操作是进栈和出栈,因此,又被叫作"先进后出"表. 首先了解下栈的概念: 栈是限定仅在表头进行 ...

  3. java 输出当前行号_Java编程实现获取当前代码行行号的方法示例

    本文实例讲述了Java编程实现获取当前代码行行号的方法.分享给大家供大家参考,具体如下: 最近的项目中,为了实现自定义的log类,能够输出具体的代码行行号,我通过使用StackTraceElement ...

  4. java中 enum什么意思_Java中枚举Enum的使用详解

    在某些情况下,一个类的对象时有限且固定的,如季节类,它只有春夏秋冬4个对象这种实例有限且固定的类,在 Java 中被称为枚举类: 在 Java 中使用 enum 关键字来定义枚举类,其地位与 clas ...

  5. java原生类型没有封装_Java基本数据类型与封装类型详解(int和Integer区别)

    Java基本数据类型与封装类型详解(int和Integer区别) 发布于 2020-4-19| 复制链接 摘记: int是java提供的8种原始数据类型之一.Java为每个原始类型提供了封装类,Int ...

  6. java 封装表单数据类型_Java基本数据类型与封装类型详解(int和Integer区别)

    int是java提供的8种原始数据类型之一. Java为每个原始类型提供了封装类,Integer是java为int提供的封装类(即Integer是一个java对象,而int只是一个基本数据类型).in ...

  7. java uml类图教程_Java利器之UML类图详解

    原标题:Java利器之UML类图详解 (点击上方公众号,可快速关注) 来源:伯乐在线专栏作者- Code4Android 如需转载,发送「转载」二字查看说明 前言 UML(Unified Modeli ...

  8. java 获取oracle表结构_Java导出oracle表结构实例详解

    Java导出oracle表结构实例详解 发布于 2020-7-20| 复制链接 摘记:  Java导出oracle表结构实例详解最近用到的,因为plsql是收费的,不让用,找了很多方法终于发现了这个. ...

  9. java case 多个值_Java switch多值匹配操作详解

    这篇文章主要介绍了Java switch多值匹配操作详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 我们都知道 switch 用来走流程分支,大 ...

最新文章

  1. Linux那些事儿之我是Sysfs(8)一起散散步-pathwalk
  2. python3多进程 queue 取值_【整理】python多进程之间共享queue | 勤奋的小青蛙
  3. Windows API一日一练(一)第一个应用程序 使用应用程序句柄 使用命令行参数 MessageBox函数 RegisterClass和RegisterClassEx函数
  4. 步进电机控制芯片_STK682/步进电机_STK682-010-E控制芯片 原创中文翻译
  5. SAP ABAP 编程语言里的%_c_pointer代表什么
  6. windows上使用的免费连接linux终端xshell6,xftp6下载
  7. 【链接保存】十分钟上手sklearn:特征提取,常用模型,交叉验证
  8. hcl启动设备失败_英林储罐清洗设备清洗公司2021收费
  9. Oracle数据库备份与还原命令 -- exp/imp
  10. display:inline-block 间隙
  11. 基于python的毕业设计仓库库存管理系统
  12. 计算机格式化后数据恢复的基础,格式化计算机硬盘后如何恢复原始数据_计算机的基本知识_IT /计算机_信息...
  13. K3 Cloud 数据库查询表常用语句
  14. 机顶盒ttl无法输入_请教大神,机顶盒接TTL进不了uboot模式
  15. 嵌入式单片机基础篇(十一)之电容触摸按键
  16. Meanshift均值漂移聚类算法
  17. UWP应用解除网络限制
  18. facebook登陆ios
  19. 居民身份证号码的编码规则
  20. JAVA实现排列组合

热门文章

  1. Redis实现点赞功能
  2. C语言中关于二进制的换算
  3. open3d中的kd树详解
  4. 浙江海盐已经试行“核供暖”,南方到底该不该供暖?南方人顶起~
  5. win10 下Adobe Reader XI闪退问题解决
  6. 三大运营商的网上流量卡数据对比,看看你会选哪一个?
  7. 圣帕特里克 VoxEdit 大赛
  8. 人工智能(Artificial Intelligence-AI)、机器学习(Machine Learning)、深度学习(Deep Learning)之间区别
  9. Redisson封装及应用实例
  10. c31 rotc_百度百科