上一篇:synchronized底层原理

上一篇我们讲解了synchronized底层原理,使用了Monitor监视器锁。 如果仅仅从Java层面,我们是看不出来synchronized在多线程竞争锁资源下一个详细的过程。
接下来,我们就研究一下Monitor底层原理,让我们一起分析一下synchronized底层是如何竞争锁资源的。

理解Monitor监视器锁原理

任何一个对象都有一个Monitor与之关联,当且一个Monitor被持有后,它将处于锁定状态。Synchronized在JVM里的实现都是 基于进入和退出Monitor对象来实现方法同步代码块同步
虽然具体实现细节不一样,但是都可以通过成对的MonitorEnter和MonitorExit指令来实现。

1.MonitorEnter和MonitorExit

1.1 MonitorEnter

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

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

b. 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1;(体现可重入锁)

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

1.2MonitorExit

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

1.2.1 解释monitorexit出现两次

monitorexit,指令出现了两次,第1次为同步正常退出释放锁;第2次为发生异步退出释放锁;

2.为什么只有在同步的代码块或者方法中才能调用wait/notify等方法?

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

Java中的所有对象的顶级父类是Object类,Object 类定义了 wait(),notify(),notifyAll() 方法,底层用的就是monitor机制,这也是为什么synchronize锁的是整个对象。

3.解释ACC_SYNCHRONIZED

看一个同步方法:

package it.yg.juc.sync;
public class SynchronizedMethod { public synchronized void method() {   System.out.println("Hello World!");   }
}

反编译结果:

从编译的结果来看,方法的同步并没有通过指令 monitorenter 和 monitorexit 来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了 ACC_SYNCHRONIZED 标示符。

JVM就是根据该标示符来实现方法的同步的:
当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。

4.小结

同步方法(无论静态或者非静态)是通过方法中的 access_flags 中设置ACC_SYNCHRONIZED标志来实现;同步代码块是通过 monitorenter(进入锁) 和 monitorexit(释放锁) 来实现。

两种同步方式本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。两个指令的执行是JVM通过调用操作系统的互斥原语mutex来实现,被阻塞的线程会被挂起、等待重新调度,会导致“用户态和内核态”两个态之间来回切换,对性能有较大影响。

5.重点:从C++源码分析synchronized底层是如何进行锁资源竞争的

monitor,可以把它理解为 一个同步工具,也可以描述为 一种同步机制,它通常被 描述为一个对象。与一切皆对象一样,所有的Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。也就是通常说Synchronized的对象锁,MarkWord锁标识位为10,其中指针指向的是Monitor对象的起始地址。

5.1ObjectMonitor 底层C++源码

在Java虚拟机(HotSpot)中,Monitor是由ObjectMonitor类实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的):

class ObjectMonitor {public:
enum {OM_OK,                    // no error
OM_SYSTEM_ERROR,          // operating system error
OM_ILLEGAL_MONITOR_STATE, // IllegalMonitorStateException
OM_INTERRUPTED,           // Thread.interrupt()
OM_TIMED_OUT              // Object.wait() timed out
};
...
ObjectMonitor() {_header       = NULL; // 对象头
_count        = 0; // 记录该线程获取锁的次数
_waiters      = 0, // 当前有多少处于wait状态的thread
_recursions   = 0; // 锁的重入次数
_object       = NULL;
_owner        = NULL; // 指向持有ObjectMonitor对象的线程
_WaitSet      = NULL; // 存放处于wait状态的线程队列
_WaitSetLock  = 0 ;
_Responsible  = NULL ;
_succ         = NULL ;
_cxq          = NULL ;
FreeNext      = NULL ;
_EntryList    = NULL ; // 存放处于block锁阻塞状态的线程队列
_SpinFreq     = 0 ;
_SpinClock    = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}

解释代码中几个关键属性:
_count,记录该线程获取锁的次数(就是前前后后,这个线程一共获取了多少次锁);
_recursions ,锁的重入次数(道格李Lock下的state);
_owner 对应 The Owner,含义是持有ObjectMonitor对象的线程;
_EntryList 对应 Entry List,含义是存放处于block锁阻塞状态的线程队列(多线程下,竞争锁失败的线程会进入EntryList队列);
_WaitSet 对应 Wait Set,含义是存放处于wait状态的线程队列(正在执行代码的线程遇到wait(),会进行WaitSet队列)。

5.2通过CAS尝试把monitor的_owner字段设置为当前线程;

void ATTR ObjectMonitor::enter(TRAPS) {// The following code is ordered to check the most common cases first
// and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
Thread * const Self = THREAD ;
void * cur ;
// 通过CAS尝试把monitor的_owner字段设置为当前线程
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
if (cur == NULL) {// Either ASSERT recursions == 0 or explicitly set recursions = 0.
assert (_recursions == 0   , "invariant") ;
assert (_owner      == Self, "invariant") ;
// CONSIDER: set or assert OwnerIsThread == 1
return ;
}
// 设置之前的owner指向当前线程,说明当前线程已经持有锁,此次为重入,_recursions自增
if (cur == Self) {// TODO-FIXME: check for integer overflow!  BUGID 6557169.
_recursions ++ ;
return ;
}
// 如果之前_owner指向的BasicLock在当前线程栈上,说明当前线程是第一次进入该monitor
// 设置recursions为1,owner为当前线程,该线程成功获得锁并返回
if (Self->is_lock_owned ((address)cur)) {assert (_recursions == 0, "internal state error");
_recursions = 1 ;
// Commute owner from a thread-specific on-stack BasicLockObject address to
// a full-fledged "Thread *".
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}

从上面代码中,可以看出,if (cur == Self)),说明设置之前的owner指向当前线程,说明当前线程已经持有锁,此次为重入,_recursions自增,即 synchronized是一把可重入锁
synchronized还是一把非公平锁,新的线程进来是可以有抢占优先级的。

5.3ObjectMonitor中的队列

ObjectMonitor中有两个队列,_EntryList 和_WaitSet ,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象 ),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时:

  1. 首先会进入 _EntryList 集合,当线程获取到对象的monitor后,进入 _Owner区域并把monitor中的owner变量设置为当前线程,同时monitor中的计数器count加1;
  2. 若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒;
  3. 若当前线程执行完毕,也将释放monitor(锁)并复位count的值,以便其他线程进入获取monitor(锁);
    同时,Monitor对象存在于每个Java对象的对象头Mark Word中(存储的指针的指向),Synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因,同时notify/notifyAll/wait等方法会使用到Monitor锁对象,所以必须在同步代码块中使用。监视器Monitor有两种同步方式:互斥与协作。多线程环境下线程之间如果需要共享数据,需要解决互斥访问数据的问题,监视器可以确保监视器上的数据在同一时刻只会有一个线程在访问。

6.synchronized获取锁流程(思想)

这个流程很关键:
因为它体现的就是synchronized底层是如何设计【同步策略思想】。 并且,这个思想与接下来道格李写的【AQS的同步策略思想】很相似。

所以,这也是我一直想要突破的地方:
看源码的时候,最关键的就是思考设计者们设计这套框架,所采用的【思想】。退一万步讲,你可以不记得syn的底层c++源码具体实现,你也可以不用记得lock的Java源码底层的详细实现,但是你必须记住:synchronized和lock实现并发安全的【思想】。
结果发现:原来Java界的两个同步利器synchronized和lock,底层实现的思想竟然如此相似。

使用synchronized进行同步。

  1. 我们都知道如果t0,t1,t2在进行获取锁时,如果t0获取锁资源成功(成功的标志:CAS成功把monitor的_owner字段设置为当前线程),
  2. 那么t1和t2是不会立马挂起的,而是先通过CAS自旋的方式再次尝试获取锁,
  3. 如果失败则入队列(EntryList队列)。

所谓“自旋”,就monitor并不把线程阻塞放入排队队列,而是去执行一个无意义的循环(因为线程阻塞涉及到用户态和内核态切换的问题),循环结束后看看是否锁已释放并直接进行竞争上岗步骤,如果竞争不到继续自旋循环,循环过程中线程的状态一直处于running状态。明显自旋锁使得synchronized的对象锁方式在线程之间引入了不公平。但是这样可以保证大吞吐率和执行效率。

不过虽然自旋锁方式省去了阻塞线程的时间和空间(队列的维护等)开销,但是长时间自旋也是很低效的。所以自旋的次数一般控制在一个范围内,例如10,50等,在超出这个范围后,线程就进入排队队列。

结语:

synchronized底层C++源码分析至此。
给自己最大的感触就是:我是看完AQS底层Java源码后,在接触到道格李解决同步策略的【思想】后,然后突然想到synchronized是如何解决同步的思想的,继而又研究了synchronized底层是如何解决同步问题的。
结果发现,二者有着惊人的相似之处:

1.多线程竞争锁,当某个线程竞争成功,其他线程都是先自旋,如果失败,然后入队列
2.竞争锁成功的线程,都会被记录在一个变量中,这个变量返回的就是当前竞争锁成功的线程对象;
3.可重入锁状态都是通过一个变量记录的;


那么有个问题来了,我们知道synchronized加锁加在对象上,对象是如何记录锁状态的呢?答案是锁状态是被记录在每个对象的对象头(Mark Word)中,下面我们一起认识一下对象的内存布局。

下一篇:Java对象内存布局

理解Monitor监视器锁原理相关推荐

  1. java什么是monitor和Monitor监视器锁、对象布局

    文章目录 Monitor监视器锁 什么是moniter 对象布局 Monitor监视器锁 每个同步对象都有一个自己的Monitor(监视器锁),加锁过程如下图所示: 任何一个对象都有一个Monitor ...

  2. synchronized底层原理—Monitor监视器

    深入了解synchronized 文章目录 深入了解synchronized 1. 作用 2. 底层实现原理 1. Monitor类 2. 管程 3. ObjectMonitor 4. _WaitSe ...

  3. 【重难点】【JUC 04】synchronized 原理、ReentrantLock 原理、synchronized 和 Lock 的对比、CAS 无锁原理

    [重难点][JUC 04]synchronized 原理.ReentrantLock 原理.synchronized 和 Lock 的对比.CAS 无锁原理 文章目录 [重难点][JUC 04]syn ...

  4. Monitor监视器对象

    在分析完对象头以后,我们知道对象头里其实是有一个重量级锁的指针,而重量级锁的指针指向的就是monitor监视器对象. synchronized无论是修饰代码块还是修饰普通方法和静态方法,本质上还都是作 ...

  5. Java Synchronized 重量级锁原理深入剖析上(互斥篇)

    前言 线程并发系列文章: Java 线程基础 Java 线程状态 Java "优雅"地中断线程-实践篇 Java "优雅"地中断线程-原理篇 真正理解Java ...

  6. 06. 多线程锁原理

    一.锁的分类 1.什么是锁 锁 大门 很多陌生人(线程)就进不去,很安全; 如果没有锁,无法锁住这个大门,很多陌生人都可以进去,不安全; 人进去后,再锁住(线程进去后,锁定) 那么在外面的很多人就进不 ...

  7. 分布式锁原理——redis分布式锁,zookeeper分布式锁

    首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法.变量. 在一个进程中,也就是一个jvm 或者说应用中,我们很容易去处理控制,在j ...

  8. 关于分布式锁原理的一些学习与思考:redis分布式锁,zookeeper分布式锁

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:牛人 20000 字的 Spring Cloud 总结,太硬核了~ 作者:队长给我球. 出处:https://w ...

  9. 一分钟理解Java公平锁与非公平锁

    转载自  一分钟理解Java公平锁与非公平锁 和朋友聊天他提到:ReentrantLock 的构造函数可以传递一个 bool 数据,true 时构造的是"公平锁".false 时构 ...

  10. zookeeper 分布式锁_关于redis分布式锁,zookeeper分布式锁原理的一些学习与思考

    编辑:业余草来源:https://www.xttblog.com/?p=4946 首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法 ...

最新文章

  1. NewSQL——优化的SQL存储引擎(TokuDB, MemSQL)+?
  2. 【opencv】9.批量命名图片文件std::sprintf
  3. 3.改变 HTML 内容
  4. ChildWindow在Open时旋转出现
  5. Python 装饰器详解(下)
  6. uva 11419 最大匹配(最小点覆盖)
  7. ACE_Reactor学习1 总体计划
  8. 801机械设计2017题签
  9. 动易html编辑器漏洞,动易2006_SP6最新漏洞得到管理员密码
  10. php中smarty模板下载,Smarty模板下载|
  11. 用canvas实现方块的放大旋转效果
  12. 中国大学慕课公开课-《视听语言》-学习笔记-1
  13. 均值定理最大值最小值公式_超急关于不等式最大值最小值的求法
  14. 输入行数,输出一个字母回文金字塔(c语言)
  15. python numpy 二维数组reshape成三维数组
  16. linux串口驱动安装 RPM,Devart数据库工具【教程】:在Linux(DEB / RPM)上安装和配置ODBC驱动程序...
  17. 浏览器 重定向次数限制_浏览器重定向(302)限制问题
  18. Markdown语法大全(超级版)
  19. MySql性能优化之分区表
  20. m3u8 php vob 服务器,使用ffmpeg下载m3u8

热门文章

  1. 【教学类-20-02】20221203《世界杯16强国旗-定量版》(大班)
  2. python随机猜数字游戏_python,random随机数,简单的python猜数字游戏
  3. 以《简单易懂》的语言带你搞懂无监督学习算法【附Python代码详解】机器学习系列之K-Means篇
  4. html里获得农历时间,获取阴历(农历)和当前日期的js代码_javascript技巧
  5. java碳纤维山地车车架咋样_自行车碳纤维车架值得买吗?它有哪些优缺点?老骑手来给你答案!...
  6. The way的用法
  7. VEMD11940FX01光学传感器
  8. 引用次数在15000次以上的都是什么神仙论文?
  9. 假币问题POJ2692
  10. Leetcode 318. Maximum Product of Word Lengths