热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Java中自旋锁的实现

nsitionalENhttp:www.w3.orgTRxhtml1DTDxhtml1-transitional.dtd

Java中初始是使用mutex互斥锁,因为互斥锁是会线程等待挂起,而对获取锁后的操作时间比较短暂的应用场景来说,这样的锁会让竞争锁的线程不停的park,unpark 的操作,这样的系统的调用性能是非常糟糕的,为了提高锁的性能,java 在6 默认使用了自旋锁。

在Linux中本身就已经提供了自旋锁的系统调用,在glibc-2.9中就有它的比较简单的实现方法

  1. int pthread_spin_lock (lock) pthread_spinlock_t *lock;  
  2. {  
  3. asm ("\n"  
  4. "1:\t" LOCK_PREFIX "decl %0\n\t"  
  5. "jne 2f\n\t"  
  6. ".subsection 1\n\t"  
  7. ".align 16\n"  
  8. "2:\trep; nop\n\t"  
  9. "cmpl $0, %0\n\t"  
  10. "jg 1b\n\t"  
  11. "jmp 2b\n\t"  
  12. ".previous"  
  13. "=m" (*lock)  
  14. "m" (*lock));  
  15. return 0;  
  16. }  
通过总线锁把参数-1保证了减法的原子性,如果减后的值是(0)的代表获得锁,其他线程的线程自旋直到参数变成初始值(1),继续竞争锁,直到获得这把锁。

Java 并没有使用系统自带的自旋锁,自己重写了自旋锁的逻辑,并且增加了自旋的次数的控制。详细见-XX:+UseSpinning 和 -XX:PreBlockSpin=xx

让我们具体来看是如何实现的,注意这是mutex锁中所实现的lock,而并不是synchinized 的锁的spin lock的实现(这个你可以参考synchronizer.cpp里的方法TrySpin_VaryDuration)

  1. int Monitor::TrySpin (Thread * const Self) {  
  2.   if (TryLock())    return 1 ;  
  3.   if (!os::is_MP()) return 0 ;    
  4.   
  5.   int Probes  = 0 ;  
  6.   int Delay   = 0 ;  
  7.   int Steps   = 0 ;  
  8.   int SpinMax = NativeMonitorSpinLimit ;  
  9.   int flgs    = NativeMonitorFlags ;  
  10.   for (;;) {  
  11.     intptr_t v = _LockWord.FullWord;  
  12.     if ((v & _LBIT) == 0) {  
  13.       if (CASPTR (&_LockWord, v, v|_LBIT) == v) {  
  14.         return 1 ;  
  15.       }  
  16.       continue ;  
  17.     }  
  18.   
  19.     if ((flgs & 8) == 0) {  
  20.       SpinPause () ;  
  21.     }  
  22.   
  23.     // Periodically increase Delay -- variable Delay form   
  24.     // conceptually: delay *= 1 + 1/Exponent   
  25.     ++ Probes;  
  26.     if (Probes > SpinMax) return 0 ;  
  27.   
  28.     if ((Probes & 0x7) == 0) {  
  29.       Delay = ((Delay << 1)|1) & 0x7FF ;  
  30.       // CONSIDER: Delay += 1 + (Delay/4); Delay &= 0x7FF ;   
  31.     }  
  32.   
  33.     if (flgs & 2continue ;  
  34.   
  35.     // Consider checking _owner's schedctl state, if OFFPROC abort spin.   
  36.     // If the owner is OFFPROC then it's unlike that the lock will be dropped   
  37.     // in a timely fashion, which suggests that spinning would not be fruitful   
  38.     // or profitable.   
  39.   
  40.     // Stall for "Delay" time units - iterations in the current implementation.   
  41.     // Avoid generating coherency traffic while stalled.   
  42.     // Possible ways to delay:   
  43.     //   PAUSE, SLEEP, MEMBAR #sync, MEMBAR #halt,   
  44.     //   wr %g0,%asi, gethrtime, rdstick, rdtick, rdtsc, etc. ...   
  45.     // Note that on Niagara-class systems we want to minimize STs in the   
  46.     // spin loop.  N1 and brethren write-around the L1$ over the xbar into the L2$.   
  47.     // Furthermore, they don't have a W$ like traditional SPARC processors.   
  48.     // We currently use a Marsaglia Shift-Xor RNG loop.   
  49.     Steps += Delay ;  
  50.     if (Self != NULL) {  
  51.       jint rv = Self->rng[0] ;  
  52.       for (int k = Delay ; --k >= 0; ) {  
  53.         rv = MarsagliaXORV (rv) ;  
  54.         if ((flgs & 4) == 0 && SafepointSynchronize::do_call_back()) return 0 ;  
  55.       }  
  56.       Self->rng[0] = rv ;  
  57.     } else {  
  58.       Stall (Delay) ;  
  59.     }  
  60.   }  
  61. }  

a. os::is_MP() 判断系统是否是多核的系统,在单核下,自旋锁是没有意义的。

b. CASPTR 使用了 Atomic::cmpxchg_ptr 原子语义 cmpxchg 比较替换,如果比较的值相等就替换成需要的值并且返回去比较的值,如果不相同返回被比较的值的内容。

在这里的语义是比较_LockWord.FullWord 和 _Lockword 的值是否相同,如果相同就把_Lockword 的值置换成v|_LBIT(_LBIT的值是1)。

自旋锁的逻辑:判断_LockWord.FullWord bit 0 是否是0,如果是0代表没有占有锁,那就尝试去占有锁,通过原子替换置bit0 为1,如果置换成功那么代表拥有锁,没有则进入自旋。

SpinPause ()  函数
在linux_x86 64位机器上 定义了
 .globl SpinPause
        .align 16
        .type  SpinPause,@function
SpinPause:
        rep
        nop
        movq   $1, %rax
        ret

主要在rep, nop 的指令经过编译器后的指令是pause,是用于提高cpu性能的,在官方上描述pase指令是为了避免memory order violation ,有种说法就是cpu是流水线的处理指令的,当原子指令store的时候,而如果有线程同时也在load他的值,那么load 必须等到store 执行成功,这样cpu就无法进行流水线作业了。但我更觉的这是个加强版的nop 也就是多增加几个空的机器周期,一来省电,二来本身spin lock就需要cpu空运行,并且不需要访问内存。

c. SafepointSynchronize::do_call_back()这是一个安全点,提供一个停止自旋锁的切入点,比如vm thread,在做线程dump, 内存 dump的时候,是需要让 自旋锁提前停止的。
 
d. if (Probes > SpinMax) return 0 ; 当大于自旋的次数的时候,自旋自动退出,也就是前面所说的参数 -XX:PreBlockSpin

最后这里还有个比较有意思的方法MarsagliaXORV (rv) ; 是算随机数的,不清楚为什么java让cpu自旋的过程中计算随机数的意义何在,为了不让cpu空转?感觉用spinpase 更合理一点。


推荐阅读
  • 腾讯安全平台部招聘安全工程师和数据分析工程师
    腾讯安全平台部正在招聘安全工程师和数据分析工程师。安全工程师负责安全问题和安全事件的跟踪和分析,提供安全测试技术支持;数据分析工程师负责安全产品相关系统数据统计和分析挖掘,通过用户行为数据建模为业务决策提供参考。招聘要求包括熟悉渗透测试和常见安全工具原理,精通Web漏洞,熟练使用多门编程语言等。有相关工作经验和在安全站点发表作品的候选人优先考虑。 ... [详细]
  • Java序列化对象传给PHP的方法及原理解析
    本文介绍了Java序列化对象传给PHP的方法及原理,包括Java对象传递的方式、序列化的方式、PHP中的序列化用法介绍、Java是否能反序列化PHP的数据、Java序列化的原理以及解决Java序列化中的问题。同时还解释了序列化的概念和作用,以及代码执行序列化所需要的权限。最后指出,序列化会将对象实例的所有字段都进行序列化,使得数据能够被表示为实例的序列化数据,但只有能够解释该格式的代码才能够确定数据的内容。 ... [详细]
  • Android中高级面试必知必会,积累总结
    本文介绍了Android中高级面试的必知必会内容,并总结了相关经验。文章指出,如今的Android市场对开发人员的要求更高,需要更专业的人才。同时,文章还给出了针对Android岗位的职责和要求,并提供了简历突出的建议。 ... [详细]
  • Python语法上的区别及注意事项
    本文介绍了Python2x和Python3x在语法上的区别,包括print语句的变化、除法运算结果的不同、raw_input函数的替代、class写法的变化等。同时还介绍了Python脚本的解释程序的指定方法,以及在不同版本的Python中如何执行脚本。对于想要学习Python的人来说,本文提供了一些注意事项和技巧。 ... [详细]
  • 本文介绍了在Win10上安装WinPythonHadoop的详细步骤,包括安装Python环境、安装JDK8、安装pyspark、安装Hadoop和Spark、设置环境变量、下载winutils.exe等。同时提醒注意Hadoop版本与pyspark版本的一致性,并建议重启电脑以确保安装成功。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • Java String与StringBuffer的区别及其应用场景
    本文主要介绍了Java中String和StringBuffer的区别,String是不可变的,而StringBuffer是可变的。StringBuffer在进行字符串处理时不生成新的对象,内存使用上要优于String类。因此,在需要频繁对字符串进行修改的情况下,使用StringBuffer更加适合。同时,文章还介绍了String和StringBuffer的应用场景。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • 本文讨论了在手机移动端如何使用HTML5和JavaScript实现视频上传并压缩视频质量,或者降低手机摄像头拍摄质量的问题。作者指出HTML5和JavaScript无法直接压缩视频,只能通过将视频传送到服务器端由后端进行压缩。对于控制相机拍摄质量,只有使用JAVA编写Android客户端才能实现压缩。此外,作者还解释了在交作业时使用zip格式压缩包导致CSS文件和图片音乐丢失的原因,并提供了解决方法。最后,作者还介绍了一个用于处理图片的类,可以实现图片剪裁处理和生成缩略图的功能。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 合并列值-合并为一列问题需求:createtabletab(Aint,Bint,Cint)inserttabselect1,2,3unionallsel ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
  • Apache Shiro 身份验证绕过漏洞 (CVE202011989) 详细解析及防范措施
    本文详细解析了Apache Shiro 身份验证绕过漏洞 (CVE202011989) 的原理和影响,并提供了相应的防范措施。Apache Shiro 是一个强大且易用的Java安全框架,常用于执行身份验证、授权、密码和会话管理。在Apache Shiro 1.5.3之前的版本中,与Spring控制器一起使用时,存在特制请求可能导致身份验证绕过的漏洞。本文还介绍了该漏洞的具体细节,并给出了防范该漏洞的建议措施。 ... [详细]
  • Python脚本编写创建输出数据库并添加模型和场数据的方法
    本文介绍了使用Python脚本编写创建输出数据库并添加模型数据和场数据的方法。首先导入相应模块,然后创建输出数据库并添加材料属性、截面、部件实例、分析步和帧、节点和单元等对象。接着向输出数据库中添加场数据和历程数据,本例中只添加了节点位移。最后保存数据库文件并关闭文件。文章还提供了部分代码和Abaqus操作步骤。另外,作者还建立了关于Abaqus的学习交流群,欢迎加入并提问。 ... [详细]
  • Tomcat安装与配置教程及常见问题解决方法
    本文介绍了Tomcat的安装与配置教程,包括jdk版本的选择、域名解析、war文件的部署和访问、常见问题的解决方法等。其中涉及到的问题包括403问题、数据库连接问题、1130错误、2003错误、Java Runtime版本不兼容问题以及502错误等。最后还提到了项目的前后端连接代码的配置。通过本文的指导,读者可以顺利完成Tomcat的安装与配置,并解决常见的问题。 ... [详细]
author-avatar
打篮球的乔巴
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有