synchronized基本锁机制
偏向锁和轻量级锁是Java SE1.6中为了提高synchronized并发性能而引入的锁机制,java 1.6之前synchronized只有重量级锁实现。
java中每一个对象都能作为锁,而synchronized有三种加锁方式:
- 普通方法加锁,锁是当前实例对象
- 静态方法加锁,锁是类对象
- 方法加锁,锁是Synchronized括号中的对象
加锁后,monitorenter指令会插入到同步代码块开始位置,monitorexit会插入到同步代码块结束位置或者异常处,然后代码执行到同步代码块时会尝试获取对象的锁。
synchronized使用的锁是存放在Java对象头的Markword里的,不同锁时存储的锁结构不同:

markword存储的数据会随着锁标志位的变化而变化,初次加锁后,数据状态会从无锁变为偏向锁,随着锁竞争的频繁,锁会逐步升级为轻量级锁然后时重量级锁。
偏向锁
- 使用场景:适用于只有一个线程获取锁,且无竞争的场景。
- 特点:
- 持锁线程不会主动释放锁。
- 无竞争情况下,持锁线程在此获取锁不需要进行CAS操作。
- 获取锁流程:
- 在线程栈帧中增加锁记录。
- 判断是否是偏向锁。
- 不是偏向锁,走锁升级操作。
- 是偏向锁,则判断markword是否偏向当前线程
- 是,获取锁。
- 不是(说明存在锁竞争),走撤销偏向锁流程。
- 撤销锁流程:
- 在全局安全点,暂停持锁线程。
- 判断持锁线程是否存活。
- 否,将对象头置为无锁状态,并根据竞争的激烈程度决定是否进行锁升级(轻或重),最后唤醒其他阻塞线程。
- 是,则进行锁升级(轻或重),唤醒持锁线程在内的阻塞线程。
轻量级锁
- 使用场景。存在锁竞争,但是锁竞争较少且竞争时间不长的情况。
- 特点。在基本上没有竞争冲突时性能较好,避免了阻塞前后上下文切换的时间。
- 获取锁流程。
- 在线程栈帧中添加锁记录。
- 将锁对象头中的Markword(如果锁被其他线程持有,通过指针找到栈帧中的锁记录后,从锁记录中获取)存储到锁记录中。
- 使用CAS操作尝试将锁对象头的Markword替换为锁记录的地址。
- 如果成功,则当前对象获取锁。
- 如果失败,表示其他线程持有锁,便通过自旋尝试获取锁。自旋一定次数后仍没有获取锁,则修改锁标志位,膨胀为重量级锁,当前线程阻塞。
- 释放锁流程。
- 使用CAS操作尝试将锁记录中的Markword替换回锁对象头。
- 如果成功,表示没有竞争或者竞争时间不长,释放锁。
- 如果失败,表示存在一定程度竞争(锁已升级),释放锁,并唤醒其他等待线程。
- 使用CAS操作尝试将锁记录中的Markword替换回锁对象头。
重量级锁
jvm中每一个java对象都会有一个monitor对象,monitor对象会和对象一同创建和一同销毁,它的作用是保证只有一个线程能访问同步代码块。在hotspot虚拟机中它的实现是ObjectMonitor。
ObjectMonitor() {
_count = 0; //锁计数器
_ownner = NULL;
_WaitSet = NULL; // 出于wait状态的线程,会被加入到WaitSet
_EntryList = NULL: // 出于等待锁block状态的线程,会被加入到该列表
}

- 使用场景。锁竞争频繁时(但这时一般也不会基于synchronized关键字加锁使用了)。
- 特点。竞争失败即阻塞,上下文切换时间耗时。
- 锁关联对象。如果synchronized给对象上锁,对象头的markword会被替换为monitor对象地址。
- 获取锁流程。
- 判断锁对象的monitor对象的owner是否为null。
- 如果是,则将monitor对象的owner修改为当前线程地址。
- 如果不是,则将当前线程放入EntryList队列中,并阻塞当前线程。
- 判断锁对象的monitor对象的owner是否为null。
- 释放锁流程。
- 将owner置为null,并唤醒阻塞队列和等待队列中的线程。
发表回复