Java的wait()、notify()学习三部曲之二:修改JVM源码看参数
2017-09-10 16:25
609 查看
在上一章《 Java的wait()、notify()学习三部曲之一:JVM源码分析》中,我们通过JVM源码分析了线程同步的相关操作,但还是留下了一些疑惑未解:在notify()和释放锁的时候,对等待锁的线程的处理有多个分支,具体走到哪个分支视Policy和QMode的值而定,今天我们实战一次,修改JVM源码将这两个参数在虚拟机运行的时候打印出来看看;
编译JVM源码需要搭建编译环境,推荐使用docker,因为我已准备好了一个完善的编译环境镜像,详情请参照《极速体验编译openjdk8(docker环境》) ;
如果您用的是linxu或Mac操作系统,那么可直接安装官方docker软件;
如果您用的是win10专业版,也能直接安装官方docker软件;
如果是win10家庭版是无法安装docker的,这时可以装vmware,再安装linux系统,推荐centos7,再在centos7上安装docker,参考[《极速体验编译openjdk8(docker环境) 》](http://blog.csdn.net/boling_cavalry/article/details/77623193
);
在docker上启动了bolingcavalry/bolingcavalryopenjdk:0.0.1镜像后:
a. 执行docker exec -it compilejdk/bin/bash进入容器;
b. 执行vi /usr/local/openjdk/hotspot/src/share/vm/runtime/objectMonitor.cpp打开要修改的文件;
c. 找到方法void ObjectMonitor::notify(TRAPS),找到方法内代码:“int Policy = Knob_MoveNotifyee ;”,在下面新增一行“printf(“*** Policy : %d\n”, Policy);”,如下图:
d. 找到方法void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS),找到方法内代码”int QMode = Knob_QMode ;”,在下面新增一行“printf(“*** QMode : %d\n”, QMode);”,如下图:
e. 在/usl/local/openjdk/目录下执行./configure –with-debug-level=slowdebug完成配置检查;
f. 在/usl/local/openjdk/目录下执行bluemake all ZIP_DEBUGINFO_FILES=0 DISABLE_HOTSPOT_OS_VERSION_CHECK=OK CONF=linux-x86_64-normal-server-slowdebug开始编译,大约20分钟,编译完成,如下图:
g. 编译完成后进入目录,创建文件NotifyDemo.java文件,内容如下:
h. 在此目录下执行./javac NotifyDemo.java编译源码;
i. 在此目录下执行./java NotifyDemo执行class,可以看到输出如下图:
如上图所示,已将运行时的Policy和QMode打印出来,我们来分析一下吧:
首先,Policy=2,表示线程A从等待队列_WaitSet中被取出,又因为_EntryList为空,所以A放入了_EntryList首位,BlOCKING状态的线程C在_cxq,所以A和C放在不同的队列中:
其次,QMode=0,在ObjectMonitor::exit方法中,对QMode等于1、2、3、4的时候都有特殊处理(例如从_EntryList中取出数据),但是对QMode等于0没有特殊处理,而是依次从_EntryList中取出线程来唤醒,如下图,由于A放在_EntryList中,所以A总是先唤醒;
通过日志确定参数值,在结合代码分析,我们把上一章的遗留问题已经搞清楚了,在下一章中,我们会继续修改源码,操控线程A和C对锁的抢占顺序。
编译JVM源码需要搭建编译环境,推荐使用docker,因为我已准备好了一个完善的编译环境镜像,详情请参照《极速体验编译openjdk8(docker环境》) ;
如果您用的是linxu或Mac操作系统,那么可直接安装官方docker软件;
如果您用的是win10专业版,也能直接安装官方docker软件;
如果是win10家庭版是无法安装docker的,这时可以装vmware,再安装linux系统,推荐centos7,再在centos7上安装docker,参考[《极速体验编译openjdk8(docker环境) 》](http://blog.csdn.net/boling_cavalry/article/details/77623193
);
在docker上启动了bolingcavalry/bolingcavalryopenjdk:0.0.1镜像后:
a. 执行docker exec -it compilejdk/bin/bash进入容器;
b. 执行vi /usr/local/openjdk/hotspot/src/share/vm/runtime/objectMonitor.cpp打开要修改的文件;
c. 找到方法void ObjectMonitor::notify(TRAPS),找到方法内代码:“int Policy = Knob_MoveNotifyee ;”,在下面新增一行“printf(“*** Policy : %d\n”, Policy);”,如下图:
d. 找到方法void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS),找到方法内代码”int QMode = Knob_QMode ;”,在下面新增一行“printf(“*** QMode : %d\n”, QMode);”,如下图:
e. 在/usl/local/openjdk/目录下执行./configure –with-debug-level=slowdebug完成配置检查;
f. 在/usl/local/openjdk/目录下执行bluemake all ZIP_DEBUGINFO_FILES=0 DISABLE_HOTSPOT_OS_VERSION_CHECK=OK CONF=linux-x86_64-normal-server-slowdebug开始编译,大约20分钟,编译完成,如下图:
g. 编译完成后进入目录,创建文件NotifyDemo.java文件,内容如下:
public class NotifyDemo { private static void sleep(long sleepVal){ try{ Thread.sleep(sleepVal); }catch(Exception e){ e.printStackTrace(); } } private static void log(String desc){ System.out.println(Thread.currentThread().getName() + " : " + desc); } Object lock = new Object(); public void startThreadA(){ new Thread(() -> { synchronized (lock){ log("get lock"); startThreadB(); log("start wait"); try { lock.wait(); }catch(InterruptedException e){ e.printStackTrace(); } log("get lock after wait"); log("release lock"); } }, "thread-A").start(); } public void startThreadB(){ new Thread(()->{ synchronized (lock){ log("get lock"); startThreadC(); sleep(100); log("start notify"); lock.notify(); log("release lock"); } },"thread-B").start(); } public void startThreadC(){ new Thread(() -> { synchronized (lock){ log("get lock"); log("release lock"); } }, "thread-C").start(); } public static void main(String[] args){ new NotifyDemo().startThreadA(); } }
h. 在此目录下执行./javac NotifyDemo.java编译源码;
i. 在此目录下执行./java NotifyDemo执行class,可以看到输出如下图:
如上图所示,已将运行时的Policy和QMode打印出来,我们来分析一下吧:
首先,Policy=2,表示线程A从等待队列_WaitSet中被取出,又因为_EntryList为空,所以A放入了_EntryList首位,BlOCKING状态的线程C在_cxq,所以A和C放在不同的队列中:
其次,QMode=0,在ObjectMonitor::exit方法中,对QMode等于1、2、3、4的时候都有特殊处理(例如从_EntryList中取出数据),但是对QMode等于0没有特殊处理,而是依次从_EntryList中取出线程来唤醒,如下图,由于A放在_EntryList中,所以A总是先唤醒;
通过日志确定参数值,在结合代码分析,我们把上一章的遗留问题已经搞清楚了,在下一章中,我们会继续修改源码,操控线程A和C对锁的抢占顺序。
相关文章推荐
- Java的wait()、notify()学习三部曲之三:修改JVM源码控制抢锁顺序
- Java的wait()、notify()学习三部曲之一:JVM源码分析
- 为什么 java wait/notify 必须与 synchronized 一起使用,jvm究竟做了些什么
- java notify()、 notifyAll()、 wait()、sleep()、yield()、join()方法学习
- Java核心知识点学习----多线程并发之线程间的通信,notify,wait
- java多线程学习之线程通信-wait()等待 notify()唤醒
- Android菜鸟之学习android源码之二(SystemUI导航栏初步认识及修改)
- Java基础学习总结(120)——JVM 参数使用详细说明
- JAVA源码剖析之---Object类(三)---toString,wait,notify,notifyAll,finalize方法的使用
- Java 多线程学习笔记:wait、notify、notifyAll的阻塞和恢复
- java 中的 wait 和 notify 实现的源码分析
- Java 高并发程序设计学习笔记--wait 和notify
- java学习-【转】JVM JSTAT命令的用法和参数讲解
- Java学习整理之Object的wait和notify方法
- Java虚拟机jvm学习二:常用JVM配置参数
- Java学习笔记81. 线程间的通信 —— wait( ),notify( ),和 notifyAll( )
- [Java学习] Java虚拟机(JVM)参数简介
- Java 多线程学习笔记 线程通讯 wait notify notifyAll的理解
- java基础知识回顾之java Thread类学习(七)--java多线程通信等待唤醒机制(wait和notify,notifyAll)
- java wait 和notify实现源码