0
点赞
收藏
分享

微信扫一扫

JAVA并发(2)—sync关键字和Monitor管程的关系

  1. sync到底是对什么加锁
  2. Monitor模型
    2.1 为什么sync是非公平锁
    2.2 Monitor和对象头的关系
    2.3 Monitor的JDK源码
  3. sync各种锁与Monitor的关系
  4. wait和notify方法的疑问
    4.1 wait和notify为什么必须synchronized中使用
    4.2 wait和notify为什么是Object的方法

1. sync到底是对什么加锁

JAVA虚拟机给每个对象和class字节码文件都设置了一个监控器Monitor,用于检测并发代码的重入。同时,Object类中提供notify和wait方法来对Monitor中的线程进行控制。

sync锁是一个可重入的非公平独占锁。sync加锁(此处特指重量级锁)会去获取obj的Monitor,如果Monitor已经被其他线程获取,那么当前线程会进入Entry Set。等待其他线程释放obj的Monitor。

而这里的Monitor可以是类.class的Monitor,也可以是当前对象(this)Monitor。

也就是可以回答上面的问题:锁实例方法是同一个对象互斥,锁静态方法是全局互斥。

  1. 作用于实例方法时,锁住的是this对象为锁的所有代码块;
  2. 作用于静态方法时,锁住的是Class实例,又因为Class相关数据存储在永久代,永久代是全局贡献,因此静态方法相当于类的一个全局锁,会锁住调用该方法的所有线程;
  3. 作用于一个对象实例时,锁住的是所有以该对象为锁的所有代码块;

即sync借助obj中的Monitor完成线程的阻塞。

2. Monitor模型

Monitor实现对临界资源的保护,保证每次只有一个线程能进入代码块进行访问。进入代码块即为持有Monitor,退出代码块即为释放Monitor。

2.1 为什么sync是非公平锁

而未抢占到锁的资源,便会进入Monitor的Entry Set阻塞。当抢占到锁的方法遇到wait()方法后,会释放Monitor资源,并进入到Wait Set阻塞。当Monitor资源被释放后。Entry Set、Wait Set和刚进入Monitor的线程共同争夺Monitor资源。这就是sync是非公平锁的原因。

2.2 Monitor和对象头的关系

sync是借助obj的monitor对象实现当前线程的阻塞。代码块加锁是在前后分别加上monitorentrymonitorexit指令来实现的。obj被加锁后,可以在其对象头的mark word的标记位体现。

2.3 Monitor的JDK源码

请点击获取objectMonitor.hpp源码...

JDK源码如下图所示,可以看到Wait Set、Entry Set、count(重入次数)、owner(持有锁的线程)等属性。

  ObjectMonitor() {
    _header       = NULL;
   //获取管程锁的次数
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    //持有该ObjectMonitor线程的指针
    _owner        = NULL;     
    //管程的条件变量的资源等待队列
    _WaitSet      = NULL;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    //管程的入口线程队列
    _EntryList    = NULL ;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

3. sync各种锁与Monitor的关系

偏向锁和轻量级锁的目的:偏向锁、轻量级锁并不是来取代重量级锁的。而是在不同的场景下的相互补充。偏向锁和轻量级锁解决的是当临界资源没有被争夺访问的场景下,如何优化性能。

偏向锁和轻量级锁的实现:通过对象头mark word的标记位,通过CAS来实现访问。无需借助Monitor。

锁可以从偏向锁升级到轻量级锁,再升级到重量级锁。这种升级过程叫做锁膨胀。

只有一个线程访问sync保护的临界资源。而偏向锁的目的是某个线程获取锁后,消除这个线程重入的开销。偏向锁只需要在置换ThreadId的时候依赖一次CAS。

两个线程交替访问,没有多线程竞争的前提下,减少传统重量级锁产生的性能损耗。

若存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

借助obj的Monitor对象完成线程的互斥访问。而线程的阻塞借助操作系统完成。这个过程是比较耗费事件的。

在JDK1.6之后,sync关键字进行了优化,使用了自旋锁,让并发的线程先不借助monitor进行同步操作。而是自旋一段时间,等待临界区线程执行完毕。

4. wait和notify方法的疑问

4.1 wait和notify为什么必须synchronized中使用

只因为synchronized关键字(重量级锁,开启了管程),使得对象指向了ObjectMonitor对象,所以调用对象的wait()和notify等方法才会将线程阻塞(加入到_WaitSet中)。

4.2 wait和notify为什么是Object的方法

又因为wait()和notify()是ObjectMonitor的方法。而Object对象头中保存了ObjectMonitor的指针,所以是Object便可操作wait()方法。

推荐阅读

Java并发基石——所谓“阻塞”:Object Monitor和AQS(1)

简书—信号量与管程

百度百科—管程

Java精通并发-通过openjdk源码分析ObjectMonitor底层实现

Java 中的 Monitor 机制

深入理解Java并发之synchronized实现原理

相关阅读

JAVA并发(1)—java对象布局
JAVA并发(2)—sync关键字和Monitor管程的关系
JAVA并发(3)—线程运行时发生GC,会回收ThreadLocal弱引用的key吗?
JAVA并发(4)— ThreadLocal源码角度分析是否真正能造成内存溢出!
JAVA并发(5)— 多线程顺序的打印出A,B,C(线程间的协作)
JAVA并发(6)— AQS源码解析(独占锁-加锁过程)
JAVA并发(7)—AQS源码解析(独占锁-解锁过程)
JAVA并发(8)—AQS公平锁为什么会比非公平锁效率低(源码分析)
JAVA并发(9)— 共享锁的获取与释放
JAVA并发(10)—interrupt唤醒挂起线程
JAVA并发(11)—AQS源码Condition阻塞和唤醒
JAVA并发(12)— Lock实现生产者消费者

举报

相关推荐

0 条评论