1.Moniter对象
1.1.Java对象头
1>.以32位虚拟机为例
①.普通对象
Klass Word表示对象的类型,它是一个指针,指向了对象所从属的class;
②.数组对象
在32位虚拟机中,integer包装类型的长度为12个字节,而int基本数据类型的长度为4个字节;
其中Mark Word结构为:
2>.64位虚拟机Mark Word结构为:
Mark Work表示对象自身运行时的数据,它里面包含了哈希值,GC分代年龄,锁状态标志,线程持有的锁,偏向线程ID,偏向时间戳等一系列信息;
更多内容请参考: what-is-in-java-object-header
1.2.Monitor(对象锁)工作原理
monitor被翻译成"监视器"或者"管程",该对象由操作系统提供;
1>.每个Java对象都可以关联一个Monitor对象,如果使用Synchronized给对象上锁(重量级)之后,该对象头的Mark Word就被设置为指向Monitor对象的指针(ptr_to_heavyweight_monitor),锁状态标识从’01’变成’10’;
2>.Monitor结构如下:
①.Moniter对象中的Owner属性指向该Monitor对象的所有者/拥有者,即获取Monitor锁的线程;
②.EntryList属性表示阻塞/等待队列,里面存放的是获取锁但是没有成功的线程;
③.WaitSet属性中存放的是已经获得锁(即已经成为monitor对象的所有者),但是条件不满足无法继续运行,为了不占用锁资源,调用wait()方法而进入WAITING状态的线程(锁会被释放,同时唤醒EntryList等待队列中处于BLOCKED阻塞状态的线程,然后这些线程进行锁竞争);
④.BLOCKED和WAITING状态的线程都处于阻塞状态,不占用CPU时间片;
⑤.BLOCKED状态的线程会在monitor所有者线程释放锁时自动被唤醒;而WAITING状态的线程需要在monitor所有者线程调用notify()或notifyAll()方法时才能被唤醒(注意此时的monitor所有者线程并不是之前的monitor所有者线程),而且唤醒之后并不意味着立刻获得锁,仍需进入EntryList等待队列中变成BLOCKED阻塞状态,等待再次被唤醒,然后参与锁竞争;
当执行其他线程中的"obj.notifyAll()或者obj.notify()"这一行代码之后,WaitSet中的WAITING状态的线程会被唤醒尝试获取对象锁,但是此时其他线程Synchronized(obj)代码还没有执行完毕,也就是说对象锁还没有释放,此时被唤醒的线程获取对象锁失败,然后这些线程会进入EntryList等待队列中变成BLOCKED状态,等待被唤醒,重新竞争对象锁;
说明:
①.刚开始obj对象关联的Monitor对象中的Owner属性为null;
②.当线程Thread-2执行synchronized(obj)时就会将obj对象关联的Monitor对象的所有者Owner置为线程Thread-2,obj对象头中的Mark Word变成/设置Monitor对象的地址,并且锁状态标识从’01’变成’10’,每个Monitor对象中只能有一个所有者Owner(即Moniter对象同一时刻只能被一个线程持有);
③.在线程Thread-2上锁的过程中(/获得锁执行同步代码过程中),如果Thread-3,Thread-4,Thread-5也来执行synchronized(obj),这些线程就会进入到EntryList等待队列中被阻塞(BLOCKED);
④.当线程Thread-2执行完同步代码块synchronized(obj)中的代码之后,就会唤醒EntryList中处于BLOCKED阻塞状态的线程来竞争锁,而竞争的过程是非公平的(并不一定是先到先得);
图中WaitSet中的线程Thread-0,Thread-1是之前获得过锁,但是条件不满足无法继续执行而进入WAITING状态的线程;
注意:
①.synchronized必须是进入同一个对象的monitor才有上述的效果;
②.不加synchronized的对象不会关联监视器Monitor,不遵循以上规则;
1.3.从字节码角度分析Monitor对象的工作原理
1>.代码
2>.编译之后的字节码
说明:
字节码中有
两个monitorexit指令
,其中第一个monitorexit指令是在synchronized同步代码块中代码正常执行完成之后使用的
,而第二个monitorexit是在synchronized同步代码块中代码执行过程中出现异常使用的,
保证synchronized同步代码块中代码执行之后可以正常释放锁,重置Monitor对象,唤醒EntryList中处于阻塞状态的线程,程序代码可以继续运行;