1synchronized关键字的底层原理
Monitor
举个例子:
1.线程1执行synchronized代码块,里面用到了lock(对象锁)。首先会让这个lock对象和monitor关联,判断monitor中的owner属性是否为null。如果为null直接获取对象锁。owner只能关联一个线程。
2.现在其他线程来了,发现owner不为空,全部进入entrylist中等待。一旦线程1执行完,把锁释放了,owner又为空了,这时候entrylist中的线程就会去抢对象锁(大家共同去抢,不是先来先得!)。
3.当线程进入synchronized同步代码块并调用wait()方法时,它会释放对该对象的锁,并将自身放入与该对象关联的monitor对象的waitset中。这样,线程就进入了等待状态,等待其他线程调用notify()或notifyAll()方法将其唤醒。
2.synchronized关键字的底层原理-进阶
1.锁升级
注:一旦发生线程竞争的情况还是要用到monitor。
2.对象锁如何关联到monitor的?
- 对象头:这部分包含了对象的元数据信息,如对象的哈希码、分代年龄、锁标志位等。对于非数组对象,对象头通常是8字节,而对于数组对象,对象头会增加4字节用于存储数组的长度。
- 实例数据:这部分包含了对象的非静态成员变量。这部分的大小取决于对象中属性的数量和类型。例如,一个
int
类型的属性会占用4字节,一个String
类型的属性会占用更多的字节。 - 对齐填充:这部分是为了确保对象的总大小是8的倍数。这是因为HotSpot VM的自动内存管理系统要求对象起始地址必须是8字节的整数倍,换句话说就是对象的大小必须是8字节的整数倍。
如果使用synchronized给对象上锁(重量级)之后,对象锁的MarkWord(对象头)中的ptr_to_heavyweight_monitor就被设置指向monitor对象的地址。由此来找到需要关联的monitor。
3.轻量级锁
static final Object obj = new Object();
public static void method1() {
synchronized (obj) {
// 同步块 A
method2();//调用method2
}
}
public static void method2() {
synchronized (obj) {//同一线程重入同一把锁
// 同步块 B
}
}
加锁过程
1.在线程栈中创建一个Lock Record,将其obj字段指向锁对象。
2.通过CAS指令将Lock Record的地址存储在对象头的mark word中(数据进行交 换),如果对象处于无锁状态则修改成功,代表该线程获得了轻量级锁。
3.如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一 部分为null,起到了一个重入计数器的作用。
4.如果CAS修改失败,说明发生了竞争,需要膨胀为重量级锁。
解锁过程
1.遍历线程栈,找到所有obj字段等于当前锁对象的Lock Record。
2.如果Lock Record的Mark Word为null,代表这是一次重入,将obj设置为null后 continue。
3.如果Lock Record的 Mark Word不为null,则利用CAS指令(保证原子性)将对象头的mark word 恢复成为无锁状态。如果失败则膨胀为重量级锁。
4.偏向锁(轻量级锁的优化)
轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。
Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现:这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争, 这个对象就归该线程所有。
static final Object obj = new Object();
public static void m1() {
synchronized (obj) {//第一次获取锁
// 同步块 A
m2();
}
}
public static void m2() {
synchronized (obj) {//第一次重入锁
// 同步块 B
m3();
}
}
public static void m3() {
synchronized (obj) {//第二次重入锁
}
}
多次重入锁,可以用偏向锁提升性能。
加锁的流程
1.在线程栈中创建一个Lock Record,将其obj字段指向锁对象。
2.通过CAS指令将Lock Record的线程id存储在对象头的mark word中,同时也设 置偏向锁的标识为101,如果对象处于无锁状态则修改成功,代表该线程获得了 偏向锁。
3.如果是当前线程已经持有该锁了,代表这是一次锁重入。设置Lock Record第一 部分为null,起到了一个重入计数器的作用。与轻量级锁不同的时,这里不会再 次进行cas操作,只是判断对象头中的线程id是否是自己,因为缺少了cas操作, 性能相对轻量级锁更好一些。
解锁流程参考轻量级锁
偏向锁只会在第一次添加锁的时候进行CAS操作,之后每一次重入只是判断当前锁是不是当前线程的。而轻量级锁每次重入都会执行一次CAS操作。因此偏向锁性能更好一点。