一.Synchronized作用
修饰静态方法:锁住当前 class,作用于该 class 的所有实例
修饰非静态方法:只会锁住当前 class 的实例
修饰代码块:该方法接受一个对象作为参数,锁住的即该对象
二.Java对象结构
2.1对象头
mark Word:主要用于存储自身运行时数据,主要表示当前Java对象的线程锁状态(无锁,偏向锁,轻量级锁,重量级锁)以及GC的标志;
Class Pointer:是指针,指向方法区中该 class 的对象,JVM 通过此字段来判断当前对象是哪个类的实例;
数组长度:当且仅当对象是数组时才会有该字段;
2.2对象体:包含了当前对象的字段和值
2.3对其字节:用于填充的字节,其目的是为了保证对象所占用的内存大小为 8 的倍数
三.锁升级过程
3.1无锁
当线程运行,没有其他线程来竞争。锁的标志位为01
3.2 偏向锁
一直只被线程A访问,没有其他线程来竞争,标志位为01, biased_lock标志来区分,1 为启用偏向锁
3.3 轻量级锁
一旦有第二个线程参与竞争,立即膨胀为轻量级锁,企图抢占的线程一开始会使用自旋的方式尝试获取锁,1.7是普通自旋,设定一个最大的自旋次数,默认是10 ,1.7之后是自适应自旋,自旋获取到锁,则自旋的次数则会增加,自旋获取不到锁次数则会减少;
3.4 重量级锁
自旋达到阈值后无法获取到锁,则会膨胀为重量级锁,其他竞争的线程此时不会自旋而是直接阻塞等待,Mark Word中的内容会变成一个监视器对象,管理排队的线程。
monitor 对象本质上是一个同步机制,保证了同时只有一个线程能够进入临界区,在 HotSpot 的虚拟机中,是由 C++ 类 ObjectMonitor 实现的。
ObjectMonitor的属性:
ContentionQueue:是个队列,所有竞争锁的线程都会先进入这个队列中,可以理解为线程的统一入口,进入的线程会阻塞。
EntryList:ContentionQueue 中有资格的线程会被移动到这里,相当于进行一轮初筛,进入的线程会阻塞。
Owner:拥有当前 monitor 对象的线程,即 —— 持有锁的那个线程。
OnDeck:与 Owner 线程进行竞争的线程,同一时刻只会有一个 OnDeck 线程在竞争。
WaitSet:当 Owner 线程调用 wait() 方法被阻塞之后,会被放到这里。当其被唤醒之后,会重新进入 EntryList 当中,这个集合的线程都会阻塞。
Count:用于实现可重入锁,synchronized 是可重入的。
3.5锁升级过程
线程A进入synchronized开始抢锁,JVM判断当前是否是偏向锁状态,根据Mark Word中的owner 线程的Id来判断。如是是直接进入临界区内执行代码;
如果不是,则通过自旋尝试获取锁,如果获取到了则将Mark Word中的线程Id改为自己的,如果自旋失败,则膨胀为轻量级锁,自旋达到阈值还未成功则升级为重量级锁
后续的竞争线程通过自旋来尝试获取锁,自旋成功那么锁的状态仍然是轻量级锁,如果竞争失败,锁会膨胀为重量级锁,后续等待的线程都会被阻塞。