站在JVM角度看Java的锁

​ 并发是从JDK 5升级到JDK 6后一项重要的改进项,HotSpot虚拟机开发团队在这个版本上花费了大量的资源去实现各类锁优化技术,如适应性自旋(Adaptive Spinning)、锁消除(Lock Elimination)、锁膨胀(Lock Coarsening)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)等,这些技术都是为了在线程之间更高效地共享数据及解决竞争问题,从而提升程序的执行效率 .数组

存在的问题

​ 对于最开始 (JDK1.5以前), Java的同步只能是一个synchronized修饰, 进行同步, 可是这个由很大的问题. 只会有一个线程能够entermonitor , 而后计数器+1. 称为重量级锁. 其余线程都被挂起, 咱们知道对于大多数JVM来讲, 线程是和操做系统的线程是一一绑定的, 也就是我操做的线程挂起须要由内核来完成, 这时候就须要用户态转换到内核态 ,而后内核执行此线程挂起, 当要恢复线程的时候再通知内核, 此时会形成很严重的问题 . 咱们知道对于CPU来讲, 他是靠时间片来实现的多线程并行执行, 若是我一个同步任务只会好比count++ , 他执行很短, 短到几ns级别, 而挂起线程和恢复线程的实现远远大于几ns , 可能大几个量级 .微信

​ 所以聪明的人想到一个事情, 就是我不让你挂起, 这么短我就本身空转一会, 也很短, (空转的意思其实就是while(true) 啥也不作,可是不是让CPU挂起,这个也称之为自旋) , 咱们知道空转就是一种浪费CPU的事情 , 可是这个浪费得有个度 , 咱们上诉的问题, 每一个线程可能空转的时间也就几ns , 可是对于长到几秒的还能空转吗, 不行了. 因此这里就是一个划分点.多线程

​ 还有一个问题就是, 好比某一段时间内, 就一个线程处于运做中, 那么此时还须要加锁操做吗 ? 是否须要优化 .并发

​ 所以引出了下文的解决方案.oop

自旋锁

​ 自旋锁是JDK1.4.2的时候引入的, 默认为关闭状态, 可使用-XX:+UseSpinning参数来开启 , 可是这个自旋锁他不是一直的自旋, 他有个度, 这个度能够用-XX:PreBlockSpin 来控制自旋多少次, 默认是10次.布局

自适应自旋

​ JDK 6中对自旋锁的优化,引入了自适应的自旋。性能

​ **自适应意味着自旋的时间再也不是固定的了,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定的。**若是在同一个锁对象上,自旋等待刚刚成功得到过锁,而且持有锁的线程正在运行中,那么虚拟机就会认为此次自旋也颇有可能再次成功,进而容许自旋等待持续相对更长的时间,好比持续100次忙循环。另外一方面,若是对于某个锁,自旋不多成功得到过锁,那在之后要获取这个锁时将有可能直接省略掉自旋过程,以免浪费处理器资源。有了自适应自旋,随着程序运行时间的增加及性能监控信息的不断完善,虚拟机对程序锁的情况预测就会愈来愈精准,虚拟机就会变得愈来愈“聪明”了。优化

Java 对象的内存布局(重要)

​ 了解轻量级锁和偏向锁 须要了解Java对象的内存布局.操作系统

再看下面以前 , 要了了解一个JAVA对象的内存结构 , 也称之为对象的内存布局线程

对象头 :

一、对象自身的运行时数据( MarkWord )

存储 hashCode、GC 分代年龄、锁类型标记、偏向锁线程 ID 、CAS 锁指向线程 LockRecord 的指针等,synconized 锁的机制与这个部分( markwork )密切相关,用 markword 中最低的三位表明锁的状态,其中一位是偏向锁位,另外两位是普通锁位。

关于markword , 这个是32位操做系统的实现,

二、对象类型指针( Class Pointer )

​ 对象指向它的类元数据的指针(这个指针相似于C语言的指针, 指针大小是根据操做系统决定的,64位好像是8个字节大小, 由于64位系统的寻址空间很大), JVM 就是经过它来肯定是哪一个 Class 的实例。

​ 若是是数组对象,还会有一个额外的部分用于存储数组长度。由于虚拟机能够经过普通Java对象的元数据信息肯定Java对象的大小,可是若是数组的长度是不肯定的,将没法经过元数据中的信息推断出数组的大小。也就是arr.len调用很方便.

实例数据区域

此处存储的是对象真正有效的信息,好比对象中全部字段的内容 . ,不管是从父类继承下来的,仍是在子类中定义的字段都必须记录起来。

​ 这部分的存储顺序会受到虚拟机分配策略参数(-XX:FieldsAllocationStyle参数)和字段在Java源码中定义顺序的影响。HotSpot虚拟机默认的分配顺序为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Pointers,OOPs)(这里基本能够肯定Java的类型也就8种),从以上默认的分配策略中能够看到,相同宽度的字段老是被分配到一块儿存放,在知足这个前提条件的状况下,在父类中定义的变量会出如今子类以前。若是HotSpot虚拟机的+XX:CompactFields参数值为true(默认就为true),那子类之中较窄的变量也容许插入父类变量的空隙之中,以节省出一点点空间。

对齐填充

​ 对象的第三部分是对齐填充,这并非必然存在的,也没有特别的含义,它仅仅起着占位符的做用。因为HotSpot虚拟机的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是任何对象的大小都必须是8字节的整数倍。对象头部分已经被精心设计成正好是8字节的倍数(1倍或者2倍),所以,若是对象实例数据部分没有对齐的话,就须要经过对齐填充来补全。

​ 其实也是为了存储方便.

​ 若是你仍是对上述不理解的话, 你就看看 <深刻理解Java虚拟机> , 里面有. 接下来就看看具体内容了 .

synchronized 锁升级流程

​ synchronized 锁并非直接进去就是一个重量级锁, 而是有所思考的, 由于不少短的操做,并不须要挂起线程. 因此相似于空转 , 还有就是单线程加锁. 何须挂起线程呢, 因此sync也帮助咱们解决了这个问题.

偏向锁

​ 在 JDK1.8 中,其实默认是轻量级锁,但若是设定了-XX:BiasedLockingStartupDelay=0 ,那在对一个 Object 作 syncronized 的时候,会当即上一把偏向锁。当处于偏向锁状态时, markwork 会记录当前线程 ID。

​ 它的意思是这个锁会偏向于第一个得到它的线程,若是在接下来的执行过程当中,该锁一直没有被其余的线程获取,则持有偏向锁的线程将永远不须要再进行同步。偏向锁解决的问题是, 有些时候就一个线程在运行, 难道还有多线程问题吗, 因此并不须要. 当出现第二个线程去竞争的状况下才会出现降级 .

原理: 当锁对象第一次被线程获取的时候,虚拟机将会把对象头中的标志位设置为“01”、把偏向模式设置为“1”,表示进入偏向模式。同时使用CAS操做把获取到这个锁的线程的ID记录在对象的Mark Word之中。若是CAS操做成功,持有偏向锁的线程之后每次进入这个锁相关的同步块时,虚拟机均可以再也不进行任何同步操做(例如加锁、解锁及对Mark Word的更新操做等)。 一旦出现另一个线程去尝试获取这个锁的状况,偏向模式就立刻宣告结束。

轻量级锁

​ 当下一个线程参与到偏向锁竞争时,会先判断 markword 中保存的线程 ID 是否与这个线程 ID 相等,若是不相等,会当即撤销偏向锁,升级为轻量级锁。每一个线程在本身的线程栈中生成一个 LockRecord ( LR ),而后每一个线程经过 CAS (自旋 )的操做将锁对象头中的 markwork 设置为指向本身的 LR 的指针,哪一个线程设置成功,就意味着得到锁。 关于 synchronized 中此时执行的 CAS 操做是经过 native 的调用 HotSpot 中 bytecodeInterpreter.cpp 文件 C++ 代码实现的,有兴趣的能够继续深挖。

重量级锁

​ 若是锁竞争加重(如线程自旋次数或者自旋的线程数超过某阈值, JDK1.6 以后,由 JVM 本身控制该规则),就会升级为重量级锁。此时就会向操做系统申请资源,线程挂起,进入到操做系统内核态的等待队列中,等待操做系统调度,而后映射回用户态。在重量级锁中,因为须要作内核态到用户态的转换,而这个过程当中须要消耗较多时间,也就是"重"的缘由之一。

可重入

​ synchronized 拥有强制原子性的内部锁机制,是一把可重入锁。所以,在一个线程使用 synchronized 方法时调用该对象另外一个 synchronized 方法,即一个线程获得一个对象锁后再次请求该对象锁,是永远能够拿到锁的。在 Java 中线程得到对象锁的操做是以线程为单位的,而不是以调用为单位的。 synchronized 锁的对象头的 markwork 中会记录该锁的线程持有者和计数器,当一个线程请求成功后, JVM 会记下持有锁的线程,并将计数器计为1。此时其余线程请求该锁,则必须等待。而该持有锁的线程若是再次请求这个锁,就能够再次拿到这个锁,同时计数器会递增。当线程退出一个 synchronized 方法/块时,计数器会递减,若是计数器为 0 则释放该锁锁。

悲观锁(互斥锁、排他锁)

​ synchronized 是一把悲观锁(独占锁),当前线程若是获取到锁,会致使其它全部须要锁该的线程等待,一直等待持有锁的线程释放锁才继续进行锁的争抢。

​ 能够参考一下 阿里中间件微信公众号的一篇文章 : mp.weixin.qq.com/s/h3VIUyH9L…

同时以上部分大部分来自于 <<深刻理解Java虚拟机>>

相关文章
相关标签/搜索