发布于2021-06-12 13:58 阅读(387) 评论(0) 点赞(21) 收藏(0)
田超凡
原创博文,仿冒必究,部分素材转载自每特教育蚂蚁课堂
javap -p -v Thread.class 查看JVM运行代码的指令
Synchronized底层基于JVM虚拟机中的C++对象Monitor和Object Monitor监视器实现,在Monitor监视器中,C++通过封装好的指令monitorenter和monitorexit来实现获取锁和释放锁
Monitorenter:获取锁-----等效于lock.lock();
Monitorexit:释放锁 ---- 等效于lock.unlock();
同步块的实现使用了monitorenter和monitorexit指令:他们隐式的执行了Lock和UnLock操作,用于提供原子性保证。
这两个指令,本质上都是对一个对象的监视器(monitor)进行获取,这个过程是排他的,也就是说同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.monitorenter
2 C++ Monitor对象解读
3 monitorenter、monitorexit指令解读
monitorenter获取锁:
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.monitorenter
每一个对象都会和一个监视器C++ monitor关联。
监视器被占用时会被锁住,其他线程无法来获取该monitor。
当JVM执行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权。
其过程如下:
1.若monior的进入数为0,线程可以进入monitor,并将monitor的进入数置为1。当前线程成为monitor的owner(所有者)
2. 若线程已拥有monitor的所有权,允许它重入monitor,进入monitor的重入数加1
3. 若其他线程已经占有monitor的所有权,那么当前尝试获取monitor的所有权的线程会被阻塞,直到monitor的进入数变为0,才能重新尝试获取monitor的所有权。
monitorexit释放锁:
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.monitorexit
当JVM执行某个线程的某个方法内部的monitorenter获取到锁之后,在同步代码执行完毕后,需要释放锁,它会尝试去释放当前对象占用的monitor的所有权。
其过程如下:
1.能执行monitorexit指令的线程一定是拥有当前对象的monitor的所有权的线程。
2.执行monitorexit时会将monitor的进入数减1。当monitor的进入数减为0时,当前线程退出monitor,不再拥有monitor的所有权,此时其他被这个monitor阻塞的线程可以尝试去获取这个monitor的所有权。
4 ACC_SYNCHRONIZED 标识同步方法
synchronized修饰的方法并没有monitorenter指令和monitorexit指令,取得代之的确实是ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法,JVM通过该ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用。这便是synchronized锁在同步代码块和同步方法上实现的基本原理。
Synchronized同步代码块和同步方法的实现原理中,二者的共同点是:
都是基于JVM中的C++监视器ObjectMonitor和Monitor实现,底层都是基于Lock锁升级过程封装的
二者区别在于:
Synchronized同步代码块,调用monitorenter和monitorexit来获取锁和释放锁;
Synchronized 同步方法,通过ACC_SYNCHRONIZED标识来声明为同步方法从而实现获取锁和释放锁。
public static synchronized void count2() {
System.out.println();
}
5 C++监视器ObjectMonitor源码分析
Java底层是使用JVM -> C++ -> HotSpot虚拟机运行的
http://hg.openjdk.java.net/jdk8 下载hotspot虚拟机
synchronized实现原理的核心:ObjectMonitor/Monitor监视器底层都是基于C++实现。
Hotspot 源码位置:
D:\code\hotspot\hotspot\src\share\vm\runtime\objectMonitor.hpp
|
6 锁池和等待池及其切换原理
如果另外的一个线程调用了相同对象的notify()方法,那么仅仅有一个处于该对象的等待池中的线程(随机)会进入该对象的锁池.
锁池和等待池切换原理:
1.如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁。
2.当有线程调用了对象的 notifyAll()方法(唤醒所有 wait 线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的线程便会进入该对象的锁池中,锁池中的线程会去重新竞争该对象锁。
3.优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用 wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了 synchronized 代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
7 wait()和notify()实现原理
调用wait方法,即可进入等待池WaitSet,线程变为WAITING状态
BLOCKED和WAITING的线程都处于阻塞状态,不占用CPU时间片
BLOCKED线程会在Owner线程释放锁的时候被唤醒,并重新进入到锁池中,准备重新竞争锁
WAITING线程会在Owner线程调用notify或notifyAll时唤醒,但唤醒后并不意味着立刻获得锁,仍需进入锁池EntryList中重新竞争锁
锁池: 存放重试多次仍然没有获取到锁而阻塞的线程
等待池:存放显式调用wait 方法的线程----
锁池和等待池的相同点是都会阻塞,且都不占用CPU时间片
备注:
notify()----只会唤醒等待中的一个线程
notifyAll()-----唤醒所有的线程
原文链接:https://blog.csdn.net/qq_30056341/article/details/117715182
作者:天花灯
链接:http://www.javaheidong.com/blog/article/222383/e7c14bbf61741223f2d4/
来源:java黑洞网
任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任
昵称:
评论内容:(最多支持255个字符)
---无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事,而不是让内心的烦躁、焦虑,坏掉你本来就不多的热情和定力
Copyright © 2018-2021 java黑洞网 All Rights Reserved 版权所有,并保留所有权利。京ICP备18063182号-2
投诉与举报,广告合作请联系vgs_info@163.com或QQ3083709327
免责声明:网站文章均由用户上传,仅供读者学习交流使用,禁止用做商业用途。若文章涉及色情,反动,侵权等违法信息,请向我们举报,一经核实我们会立即删除!