Synchronized原理解析
synchronized(this) {
i++;
}
思考这三个问题?
- this对象加锁的状态如何记录?
- 状态被记录到this对象里面了吗?
- 若锁占用,线程挂起;释放锁时,唤醒挂起的线程,是如何做到的?
堆中对象的存储
代码示例:
public class Demo5_main {
public static void main(String args[]){
int a = 1;
Teacher kody = new Teacher();
kody.stu = new Student();
}
}
class Teacher{
String name = "Kody";
int age = 40;
boolean gender = true;
Student stu;
}
class Student{
String name = "Emily";
int age = 18;
boolean gender = false;
}
在JVM中的存储结构
- 方法中定义的局部变量都存放在虚拟机栈中的局部变量表,堆中存放的是具体的值,局部变量表通过引用指向堆中的值。
- 对象中除了存放值外,还存放了对象头,对象头指向方法区中具体的类信息,与堆内存中的对象进行绑定
- 如果新创建了一个String对象(String str = new String(“abc”)),str在局部变量表中通过引用指向堆内存中的引用,堆中的引用再指向方法区中的字符串常量池。
对象头的概念
对象除了在堆中存储值外,还开辟了一个内存区域来存放对象头。
Class Metadata Address:就是通过这个来指向Class对象的
Array Length:数组对象的长度就存放在这里
Mark Word:用来存放对象锁的信息,长度为32位或64位
Mark Word
Unlocked : 未锁定
Light-weight locked : 轻量级锁
Heavy-weight locked : 重量级锁
轻量级加锁过程原理分析
- 线程进行锁的争抢,会在线程独占内存开辟一个Lock Record(锁记录空间),会将对象的Mark Word(最开始的未锁定状态 hashcode|age|0),拷贝到Lock Record中
- Lock Record 会用**CAS(Hashcode|age|0, Lock record address)**操作对Mark Word进行更改,只有一个线程会成功修改成功,另一个CAS失败,进行自旋
- 栈帧中的Lock Record内存中还有个owner,指向堆中对象的Mark Word,Mark Word也会指向这个owner,表示现在哪个线程占有了这把锁
- 当自旋达到一定次数,或者这个时候第三个线程又来争抢锁,锁就会升级为重量级锁。(自旋是一个很消耗性能的操作)
重量级锁加锁原理分析
- 重量级锁是对jvm底层的对象监视器(ObjectMonitor)来实现的,ObjectMonitor用C++来实现的
- owner :用来存放抢锁成功的线程
- entryList:阻塞池,抢锁失败的线程会进入到里面,这里面的线程状态为Blocking
- waitSet:等待池,调用wait()方法就会进入到里面,线程状态为waitting,进入等待池会释放锁
- count:synchronized是一个可重入锁,用来记录重入的次数
- 只有拿到对象的锁,才能调用notify()唤醒等待池中的线程,唤醒的线程不一定会拿到锁,所以synchronized是一个非公平锁
通过这个原理我们可以更加深入的理解线程的6种状态,以及调用wait()和notify()方法为何必须要拿到对象的锁