在上一章《
编译JVM源码需要搭建编译环境,推荐使用docker,因为我已准备好了一个完善的编译环境镜像,详情请参照《
如果您用的是linxu或Mac操作系统,那么可直接安装官方docker软件;如果您用的是win10专业版,也能直接安装官方docker软件;如果是win10家庭版是无法安装docker的,这时可以装vmware,再安装linux系统,推荐centos7,再在centos7上安装docker,参考《》;
在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 notsuspended, TRAPS),找到方法内代码"int QMode = KnobQMode ;",在下面新增一行“printf("*** QMode : %d\n", QMode);”,如下图:
e. 在/usl/local/openjdk/目录下执行./configure --with-debug-level=slowdebug完成配置检查;f. 在/usl/local/openjdk/目录下执行bluemake all ZIPDEBUGINFOFILES=0 DISABLEHOTSPOTOSVERSIONCHECK=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对锁的抢占顺序。