搜索
首页Javajava教程Java排他锁实现的代码详解

Java排他锁实现的代码详解

Oct 17, 2017 am 09:38 AM
java代码详解

这篇文章主要介绍了Java编程实现排他锁的相关内容,叙述了实现此代码锁所需要的功能,以及作者的解决方案,然后向大家分享了设计源码,需要的朋友可以参考下。

一 .前言

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

(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类型线程安全的Setf7e83be87db5cd2d9a8a0b8117b38cd4 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&#39;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排他锁实现的代码详解的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
Java开发的哪些方面取决于平台?Java开发的哪些方面取决于平台?Apr 26, 2025 am 12:19 AM

JavadevelovermentIrelyPlatForm-DeTueTososeVeralFactors.1)JVMVariationsAffectPerformanceNandBehaviorAcroSsdifferentos.2)Nativelibrariesviajnijniiniininiinniinindrododerplatefform.3)

在不同平台上运行Java代码时是否存在性能差异?为什么?在不同平台上运行Java代码时是否存在性能差异?为什么?Apr 26, 2025 am 12:15 AM

Java代码在不同平台上运行时会有性能差异。1)JVM的实现和优化策略不同,如OracleJDK和OpenJDK。2)操作系统的特性,如内存管理和线程调度,也会影响性能。3)可以通过选择合适的JVM、调整JVM参数和代码优化来提升性能。

Java平台独立性有什么局限性?Java平台独立性有什么局限性?Apr 26, 2025 am 12:10 AM

Java'splatFormentenceHaslimitations不包括PerformanceOverhead,versionCompatibilityIsissues,挑战WithnativelibraryIntegration,Platform-SpecificFeatures,andjvminstallation/jvminstallation/jvmintenance/jeartenance.therefactorscomplicatorscomplicatethe“ writeOnce”

解释平台独立性和跨平台发展之间的差异。解释平台独立性和跨平台发展之间的差异。Apr 26, 2025 am 12:08 AM

PlatformIndependendecealLowsProgramStormonanyPlograwsStormanyPlatFormWithOutModification,而LileCross-PlatFormDevelopmentRequiredquiresMomePlatform-specificAdjustments.platFormIndependence,EneblesuniveByjava,EnablesuniversUniversAleversalexecutionbutmayCotutionButMayComproMisePerformance.cross.cross.cross-platformd

即时(JIT)汇编如何影响Java的性能和平台独立性?即时(JIT)汇编如何影响Java的性能和平台独立性?Apr 26, 2025 am 12:02 AM

JITcompilationinJavaenhancesperformancewhilemaintainingplatformindependence.1)Itdynamicallytranslatesbytecodeintonativemachinecodeatruntime,optimizingfrequentlyusedcode.2)TheJVMremainsplatform-independent,allowingthesameJavaapplicationtorunondifferen

为什么Java是开发跨平台桌面应用程序的流行选择?为什么Java是开发跨平台桌面应用程序的流行选择?Apr 25, 2025 am 12:23 AM

javaispopularforcross-platformdesktopapplicationsduetoits“ writeonce,runanywhere”哲学。1)itusesbytbytybytecebytecodethatrunsonanyjvm-platform.2)librarieslikeslikeslikeswingingandjavafxhelpcreatenative-lookingenative-lookinguisis.3)

讨论可能需要在Java中编写平台特定代码的情况。讨论可能需要在Java中编写平台特定代码的情况。Apr 25, 2025 am 12:22 AM

在Java中编写平台特定代码的原因包括访问特定操作系统功能、与特定硬件交互和优化性能。1)使用JNA或JNI访问Windows注册表;2)通过JNI与Linux特定硬件驱动程序交互;3)通过JNI使用Metal优化macOS上的游戏性能。尽管如此,编写平台特定代码会影响代码的可移植性、增加复杂性、可能带来性能开销和安全风险。

与平台独立性相关的Java开发的未来趋势是什么?与平台独立性相关的Java开发的未来趋势是什么?Apr 25, 2025 am 12:12 AM

Java将通过云原生应用、多平台部署和跨语言互操作进一步提升平台独立性。1)云原生应用将使用GraalVM和Quarkus提升启动速度。2)Java将扩展到嵌入式设备、移动设备和量子计算机。3)通过GraalVM,Java将与Python、JavaScript等语言无缝集成,增强跨语言互操作性。

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热工具

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一个PHP/MySQL的Web应用程序,非常容易受到攻击。它的主要目标是成为安全专业人员在合法环境中测试自己的技能和工具的辅助工具,帮助Web开发人员更好地理解保护Web应用程序的过程,并帮助教师/学生在课堂环境中教授/学习Web应用程序安全。DVWA的目标是通过简单直接的界面练习一些最常见的Web漏洞,难度各不相同。请注意,该软件中

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

Atom编辑器mac版下载

Atom编辑器mac版下载

最流行的的开源编辑器

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

MinGW - 适用于 Windows 的极简 GNU

MinGW - 适用于 Windows 的极简 GNU

这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。