● Synchronization的底层实现概述
Java虚拟机的同步(Synchronized)是基于进入和退出管理对象(monitor)实现的。同步方法并不是由monitor enter和monitor exit指令来实现同步的,而是由方法调用指令读取运行时常量池中的方法的ACC_SYNCHRONIZED标志来隐式实现的。
注:monitor enter 和 monitor exit 指令是C语言的内容。
● 对象的内存模型
● 对象组成介绍
对象头:存储对象的 hashCode、锁信息或分代年龄或 GC 标志,类型指针指向对象的类元数据,JVM 通过这个指针确定该对象是哪个类的实例等信息。
实例变量:存放类的属性数据信息,包括父类的属性信息。
填充数据:由于虚拟机要求对象起始地址必须是 8 字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐。
● 加锁时的流程
当执行 synchronized 同步方法或同步代码块时,会在对象头中记录锁标记,锁标记指向的是 monitor 对象(也称为管程或监视器锁)的起始地址来记录锁信息。每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如 monitor 可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。
另外的线程想获取对象头中的锁信息的时候,会发现对象头中已经记录一把锁(monitor),他就获取不到。monitor是互斥的,对象头记录的monitor就不会分配给其他线程了,此时这个线程就会进入阻塞状态。当执行中的线程发生异常,或者是释放锁标记,对象头的锁信息就会释放它记录的monitor。阻塞状态的线程就会弹出来一同争夺,重新在锁信息中记录monitor。
ObjectMonitor(java的monitor的实现)中有两个队列,_WaitSet 和 _EntryList,以及_Owner标记。其中WaitSet是用于管理等待队列(wait)线程的,EntryList 是用于管理锁池阻塞线程的,_Owner 标记用于记录当前执行线程。
● 线程状态流程
当多线程并发访问同一个同步代码时,首先会进入EntryList,当线程获取锁标记后,monitor 中的Owner 记录此线程,并在 monitor 中的计数器执行递增计算( 1),代表锁定,其他线程在EntryList 中继续阻塞。若执行线程调用 wait 方法,则 monitor 中的计数器执行赋值为 0 计算,并将Owner 标记赋值为 null,代表放弃锁,执行线程进入WaitSet 中阻塞。若执行线程调用 notify/notifyAll 方法,WaitSet 中的线程被唤醒,进入EntryList 中阻塞,等待获取锁标记。若执行线程的同步代码执行结束,同样会释放锁标记,monitor 中的Owner标记赋值为 null,且计数器赋值为 0 计算。
● 什么是锁的重入
在 Java 中,同步锁是可以重入的。只有同一线程调用同步方法或执行同步代码块,对同一个对象加锁时才可重入。当线程持有锁时,会在 monitor 的计数器中执行递增计算,若当前线程调用其他同步代码,且同步代码的锁对象相同时,monitor 中的计数器继续递增。每个同步代码执行结束,monitor 中的计数器都会递减,直至所有同步代码执行结束,monitor 中的计数器为 0 时,释放锁标记,_Owner 标记赋值为 null。