无锁
@Test
public void test1() throws InterruptedException {
MyObject obj2=new MyObject();
// 无锁
System.out.println(ClassLayout.parseInstance(obj2).toPrintable());
}
com.oyb.jvm.test03.lesson07.MyObject 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) 73 22 01 f8 (01110011 00100010 00000001 11111000) (-134143373)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
总结:
当前我们的代码是没有加锁锁的,当前偏向锁为0,锁类型为01,故,无锁的对象头MarkWord格式:偏向锁为0,锁类型为01。
匿名偏向锁
@Test
public void test2() throws InterruptedException {
Thread.sleep(5000);
// JVM 启动 5 秒后创建对象
MyObject obj2=new MyObject();
// 匿名偏向锁
System.out.println(ClassLayout.parseInstance(obj2).toPrintable());
}
com.oyb.jvm.test03.lesson07.MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 73 22 01 f8 (01110011 00100010 00000001 11111000) (-134143373)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
在对象,在支持偏向之前都是无锁状态的,但是在支持偏向后,创建的对象就自动带有偏向标识,但是此时是没有偏向任何线程的,属于一个匿名偏向(anonymously biased)状态,此时对象可以偏向任何一个线程。
匿名偏向锁如何升级为偏向锁
public class MarkWordTest {
@Test
public void test2() throws InterruptedException {
// JVM 虚拟机启动的时候创建
MyObject obj1=new MyObject();
// 无锁
System.out.println(ClassLayout.parseInstance(obj1).toPrintable());
Thread.sleep(5000);
// JVM 启动 5 秒后创建对象
MyObject obj2=new MyObject();
// 匿名偏向锁
System.out.println(ClassLayout.parseInstance(obj2).toPrintable());
synchronized (obj2) {
// 偏向锁
System.out.println(ClassLayout.parseInstance(obj2).toPrintable());
}
}
}
com.oyb.jvm.test03.lesson07.MyObject 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) 73 22 01 f8 (01110011 00100010 00000001 11111000) (-134143373)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
com.oyb.jvm.test03.lesson07.MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 73 22 01 f8 (01110011 00100010 00000001 11111000) (-134143373)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
com.oyb.jvm.test03.lesson07.MyObject object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 78 3a 03 (00000101 01111000 00111010 00000011) (54163461)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) 73 22 01 f8 (01110011 00100010 00000001 11111000) (-134143373)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
Process finished with exit code 0
为什么要睡眠?
因为虚拟机在启动的时候对于偏向锁有延迟,
如果没有偏向锁的延迟的话,虚拟机在启动的时候,可能JVM某个线程调用你的线程,这样就有可能变成了轻量锁或重量锁,所以要做偏向锁的延迟
那我们怎么看到打印的对象头是偏向锁的呢?
有两种方式:
第一种是加锁之前先让线程睡几秒
第二种加上JVM的运行参数,关闭偏向锁的延迟,具体的命令如下:
-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
什么是偏向锁?
如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,这种情况下,就会给线程加一个偏向锁。
偏向锁图
匿名偏向锁 和 偏向锁的区别?
偏向锁线程ID的作用
一个线程在第一次进入同步块时,会在对象头和栈帧中的锁记录里存储锁的偏向的线程ID。
当下次线程进入这个同步块时,会去检查锁的Mark Word里面是不是放的自己的线程ID。
如果是,表名该线程已经获得了锁,以后该线程在进入和退出同步块时不需要花费CAS操作来加锁和解锁
如果不是,就表明有另一个线程来竞争这个偏向锁。
这个时候,会尝试使用CAS来替换Mark Word里面的线程ID为新线程的ID,这时候要分为两种情况:
成功,表示之前的线程不存在了,Mark Word里面的线程ID为新线程的ID,锁不会升级,仍然为偏向锁;
失败,表示之前的线程仍然存在,那么暂停之前的线程,设置偏向锁标识为0,
并设置所标志位为00,升级为轻量级锁,会按照轻量级锁的方式进行竞争锁。
Q1: 假如有线程A和线程B,以及有把锁obj,一开始,A,进入同步代码块,设置偏向锁,B进入同步代码块,记录偏向锁,
A再次进入同步代码块,
注意:这个线程ID并不是JVM分配的线程ID号,和Java Thread中的ID是两个概念。
无锁 —> 001
偏向锁 —> 101
轻量级锁 —> 000
重量级锁 —> 010
关于CAS的简单讲解:
CAS全称Compare And Swap,是一种无锁算法。
在不使用锁的情况下实现多线程之间的变量同步。
java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。
CAS算法设计到三个操作数:
- 需要读写的内存值V。
- 进行比较的值A。
- 要写入的新值B。
当且仅当V的值等于A时,CAS通过原子方式用新值B来更新V的值(“比较+更新”整体是一个原子操作),否则不会执行任何操作。
一般情况下,“更新时一个不断重试的操作”
例如:java.util.concurrent包中的原子类(例如AtomicInteger),就是通过CAS来实现了乐观锁。