预备知识https://blog.csdn.net/qq_40977118/article/details/108291922
1. 同步代码块
package org.example.test;
import org.example.entity.A;
public class SynchronizedTest {
public static void main(String[] args) {
A a = new A();
int i = 0;
synchronized (a){
i++;
}
System.out.println(i);
}
}
2. 反编译
javap -c SynchronizedTest.class
关于monitor方法的源码http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/87ee5ee27509/src/share/vm/interpreter/bytecodeInterpreter.cpp
- 代码在进入synchronized之前,a对象的对象头的锁状态应该是无锁可偏向(101)
- 当jvm遇到synchronized关键字时,会在当前线程栈中产生一个lock record对象,由object reference和_displaced_header两部分组成,object reference指向锁对象a,_displaced_header是空
- 进入到synchronized(a)代码块内以后,_displaced_header会保存a对象的mark word(61个0+101),结束后锁对象的mark word会变为A线程id+epoch+age+101
3. 偏向锁(A线程来了)
- A线程来了,第一次拿锁,判断mark word是否可偏向,在第一次拿锁不计算hashcode的情况下,一定是可偏向的;
- 判断是不是自己, 如果不是自己,判断是不是匿名偏向,第一加锁肯定是匿名的(锁没有指向任何一个线程的id),经过一次cas操作,得到一个偏向自己的偏向锁;
- 如果是自己,而且没有过期,得到偏向锁,后面同一个线程再次加锁的效率非常高,只需要判断对象头内的线程id是不是相同,偏向时间戳是不是过期即可,如果线程id相同,时间戳也没有过期,直接拿锁,少了一次cas操作。
- 如果A线程再次拿锁,还是偏向锁;注意重新加锁和重入不一样
- 重新加锁和重入的区别:重新加锁是释放之后再次加锁;重入是没有释放之后再次加锁
sync(a){//1
xxxxxx
}
sync(a){//2
zzzzz
}
//上面的叫做重新加锁;如果是1的时候是偏向;那么2也是偏向
sync(a){//1
xxxx
sync(a){//2
zzzz
}
}
//上面的代码叫做重入
- A线程代码执行结束后,锁对象的对象头内的锁状态仍然是偏向锁(101),只是前54位带了线程id
package org.example.test;
import org.example.entity.A;
import org.openjdk.jol.info.ClassLayout;
public class NoLockTest {
static A a = new A();
static Thread t1;
public static void main(String[] args) throws InterruptedException {
t1 = new Thread(){
@Override
public void run() {
synchronized (a){
System.out.println("name:"+Thread.currentThread().getName());
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
};
t1.setName("t1");
t1.start();
t1.join();
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
- 偏向锁详细处理流程
4. 轻量锁(B线程来了)
- 接着偏向锁的流程,锁已经被A线程持有,所以锁的对象头内线程id一定不是B线程id,而且又不是匿名偏向的,就会进入轻量锁加锁逻辑
- 当B线程尝试拿锁时,先把lock record的object reference指向偏向A线程的锁(101),然后设置一个无锁(001)的mark word,再将这个mark word存入_displaced_header
- 执行cas,这时只有锁状态为无锁(001)时,才会cas成功,接着将锁对象mark word(64位)变为指向lock record的指针(62位)+轻量锁(00),不会发生锁膨胀,就不会升级为重量锁;
- cas判断当前锁状态是不是无锁,如果是无锁,就把当前锁mark word存入lock record的_displaced_header,再把指向lock record的指针放入mark word中,同时变为轻量锁
- 轻量锁在退出同步代码块时,会把锁的状态置为无锁(001),lock record存入mark word是为了在释放锁时,恢复锁对象的mark word。
- 关闭偏向延迟-XX:BiasedLockingStartupDelay=0
package org.example.test;
import org.example.entity.A;
import org.openjdk.jol.info.ClassLayout;
public class NoLockTest {
static A a = new A();
static Thread t1;
static Thread t2;
public static void main(String[] args) throws InterruptedException {
t1 = new Thread(){
@Override
public void run() {
synchronized (a){
System.out.println("name:"+Thread.currentThread().getName());
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
t2.setName("t2");
t2.start();
}
};
t2 = new Thread(){
@Override
public void run() {
synchronized (a) {
System.out.println("name:"+Thread.currentThread().getName());
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
};
t1.setName("t1");
t1.start();
t1.join();
t2.join();
System.out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
name:t1
org.example.entity.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 10 c2 1e (00000101 00010000 11000010 00011110) (516034565)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 f8 (00000101 11000010 00000000 11111000) (-134168059)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
name:t2
org.example.entity.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 40 f3 88 1f (01000000 11110011 10001000 00011111) (529068864)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 f8 (00000101 11000010 00000000 11111000) (-134168059)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
org.example.entity.A object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 05 c2 00 f8 (00000101 11000010 00000000 11111000) (-134168059)
12 4 int A.i 0
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
5. 重量锁(C线程来了)
cas成功
- B线程代码执行结束,C线程来了(属于交替执行),只有锁状态为无锁(001)时,cas成功,将锁变为轻量锁,不会发生锁膨胀,就不会升级为重量锁;
cas失败
- 如果B线程代码还没执行结束,在准备cas之前,被cpu切换成执行C线程,C执行cas,并且成功了,把mark word变成001,并放入lock record的_displaced_header;mark word改成指向C线程lock record的指针,并且锁状态是轻量锁(00);这时候,cpu时间片再次切换到B线程,B接着原来的代码继续走,开始执行cas,这时候cas必定失败,因为object lock锁对象已经被C线程改成轻量锁(00),此时锁膨胀为重量锁。