码迷,mamicode.com
首页 > 编程语言 > 详细

Java锁机制

时间:2021-01-01 11:51:28      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:block   映射   对象   mutex   允许   相互   为什么   线程id   修改   

1.每个对象都拥有一把锁,这把锁存放在对象头中

技术图片

 

 

 

 

对象头 包括: Mark WordClass Point

其中Classpoint是类型指针,指向类元数据的InstanceKlass,确定该对象所属的类型

Mark Word

技术图片

 

 

 

 

2.synchronized(互斥锁)的实现原理

技术图片

 

 

1.monitor(监视器)

通过反编译(先使用javac 对.java进行编译,然后对生成的.class字节码文件进行反编译

)java可以发现在synchronized包裹的业务代码里 其实是使用 monitorenter 和monitorexit两个字节码指令 进行实现同步。

monitor相当于一个房间 一次只能被允许一个进程进入

monitorenter :

每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:

1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。

2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.

3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。

monitorexit: 

执行monitorexit的线程必须是objectref所对应的monitor的所有者。

指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

  

通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。

 

 

 

 

2.synchronized会存在性能问题

 

monitor实际上是 依赖于操作系统的mutex lock来实现的。

因为java线程 实际上操作系统线程的映射。因此 每当挂起或者唤醒一个线程都需要切换操作系统内核态,这种操作是比较重量级的。在一些情况下 线程的切换时间可能会超过线程本身执行任务的时间。因此synchronized 可能会严重影响程序的性能。

 

3.锁升级

因此java6中 引入了 无锁,偏向锁,轻量级锁,重量级锁。

锁只能升级,不能降级!!

 

1.无锁
  • 资源没有被线程竞争

  • 存在竞争,通过非锁方式同步线程。

    CAS: 多个线程想要修改同一个值,会通过比较并交换的方式 只有一个线程会修改成功,其余修改失败的线程会不断自旋尝试直到修改成功。 cas在操作系统中,通过一条cpu指令实现,因此能够保证原子性。

     

2.偏向锁

我们希望 对象最好能够认识线程。

因此在对象头 中的最后两个bit用来标识,当01时,说明当前对象(资源)是无锁或者偏向锁的一个状态,然后再判断倒数第三个bit 当其为1时,说明当前对象时偏向锁,那么他就会去判断对象头前23个bit 里存储的线程id是否是当前尝试获取对象的线程id。

  • 如果是则实现了偏向锁,如果不是,那么对象就会认为此时有多个线程需要获取资源。此时锁将会升级为轻量级 锁。

倒数第三个bit为0时,则表示无锁状态。

 技术图片

 

 

 

 

3.轻量级锁

 

当偏向锁升级为轻量级锁时,前30个bit都会变成一个指向线程独有的java虚拟机栈中 lock record区域的指针。

 

当线程发现,对象是轻量级锁时,会在自己独有的虚拟机栈中开辟一块空间 lock record。

里面将会存放mark word 的副本以及owner指针。

过程

此时线程通过cas尝试获取锁,获取成功将会获得对象头中的markword生成一个副本到 lock record中。并且将owner指针指向该对象,且对象头中前30个bit会生成一个指针指向栈中的lock record。

此时相互的指针,从而形成了 线程与资源的双向绑定。

此时别的想要获取该资源的线程将会自旋等待,

自旋:就是不断尝试去获取锁资源,这个过程不断循环。这个过程相较于被操作系统挂起阻塞,如果目标锁很快就被释放的话,自旋操作就不需要进行系统中断和现场恢复,所以效率更高。但是自旋相当于cpu空转,它是在浪费cpu资源。因此出现了一种自旋优化,叫适应性自旋,即自旋时间不在固定,而是根据该线程上一次在同一个锁上的自旋时间以及锁状态两个条件决定。

即 当前正在自旋等待的线程刚刚获取到过这把锁,当目前这把锁被别的线程占有,那么虚拟机就会认为这次自旋等待锁也很有可能会成功,因此它将允许更长的自旋时间。

 

自旋等待的线程数超过CPU核数的一半,或者自旋次数超过10次,轻量级锁将会升级为重量级锁。那就使用monitor管控资源,此时对资源的管控更加严格。

 

Java锁机制

标签:block   映射   对象   mutex   允许   相互   为什么   线程id   修改   

原文地址:https://www.cnblogs.com/suming1011/p/14197854.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!