Java并发——关键字synchronized解析
synchronized用法
在Java中,最简单粗暴的同步手段就是synchronized关键字,其同步的三种用法: ①.同步实例方法,锁是当前实例对象 ②.同步类方法,锁是当前类对象 ③.同步代码块,锁是括号里面的对象 示例:
public class SynchronizedTest {
/**
* 同步实例方法,锁实例对象
*/
public synchronized void test() {
}
/**
* 同步类方法,锁类对象
*/
public synchronized static void test1() {
}
/**
* 同步代码块
*/
public void test2() {
// 锁类对象
synchronized (SynchronizedTest.class) {
// 锁实例对象
synchronized (this) {
}
}
}
}
synchronized实现
javap -verbose查看上述示例:

case Bytecodes::_monitorenter:
{
pop_object();
set_monitor_count(monitor_count() + 1);
break;
}
case Bytecodes::_monitorexit:
{
pop_object();
assert(monitor_count() > 0, "must be a monitor to exit from");
set_monitor_count(monitor_count() - 1);
break;
}
void pop_object() {
assert(is_reference(type_at_tos()), "must be reference type");
pop();
}
void pop() {
debug_only(set_type_at_tos(bottom_type()));
_stack_size--;
}
int monitor_count() const { return _monitor_count; }
void set_monitor_count(int mc) { _monitor_count = mc; }
从源码中我们发现当线程获得该对象锁后,计数器就会加一,释放锁就会将计数器减一。
Monitor
每个对象都拥有自己的监视器,当这个对象由同步块或者这个对象的同步方法调用时,执行方法的线程必须先获取该对象的监视器才能进入同步块和同步方法,如果没有获取到监视器的线程将会被阻塞在同步块和同步方法的入口处,进入到BLOCKED状态,如图:

Monitor是线程私有的数据结构,每个线程都有一个可用monitor record列表,同时 还有一个全局的可用列表。每一个被锁住的对象都会和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。其结构如下:

Owner:初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL EntryQ:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程 RcThis:表示blocked或waiting在该monitor record上的所有线程的个数 Nest:用来实现重入锁的计数 HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age) Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁
锁优化
jdk1.6中synchronized的实现进行了各种优化,如适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁,主要解决三种场景: ①.只有一个线程进入临界区,偏向锁 ②.多线程未竞争,轻量级锁 ③.多线程竞争,重量级锁 偏向锁→轻量级锁→重量级锁过程,锁可以升级但不能降级,这种策略是为了提高获得锁和释放锁的效率,源码解析可以看占小狼——synchronized实现
偏向锁
引入偏向锁的目的是:在没有多线程竞争的情况下,尽量减少不必要的轻量级锁执行路径。相对于轻量级锁,偏向锁只依赖一次CAS原子指令置换ThreadID,不过一旦出现多个线程竞争时必须撤销偏向锁,主要校验是否为偏向锁、锁标识位以及ThreadID。

轻量级锁
引入轻量级锁的主要目的是在多线程没有竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。如果多个线程在同一时刻进入临界区,会导致轻量级锁膨胀升级重量级锁,所以轻量级锁的出现并非是要替代重量级锁,在有多线程竞争的情况下,轻量级锁比重量级锁更慢。
①.如果对象的Mark Word仍然指向着线程的锁记录,执行② ②.用CAS操作把对象当前的Mark Word和线程中复制的Displaced Mark Word替换回来,如果成功,则说明释放锁成功,否则执行③ ③.如果CAS操作替换失败,说明有其他线程尝试获取该锁,则需要在释放锁的同时需要唤醒被挂起的线程

重量级锁
重量级锁通过对象内部的监视器(monitor)实现,其中monitor的本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的切换,切换成本非常高。
感谢
芋道源码——synchronized实现原理 《深入理解Java虚拟机》 《java并发编程的艺术》
转载自:https://juejin.cn/post/6844903636552663053