Synchronized 是Java中的一种锁实现,很多资料中都有说这是一个重量级锁(对其有许多优化,轻量级锁、偏向锁等),但是剔除这些优化的情况下,为什么 Synchronized 是一个重量级锁呢?
以下的思考基于 RentrantLock 和 Synchroinzed 展开
思考1 会不会是因为Synchronized关联了一个Monitor对象呢?
某面试官对我的回答: 这仅仅只是关联了一个对象,不足以让 Synchronized 称为重量级锁
思考2 会不会是因为维护了阻塞队列和等待队列呢?
不会,因为 ReentrantLock 和 Synchronized 都有维护阻塞队列和等待队列
在我上网查阅了资料和问了大佬后,得出了以下的总结:
首先需要理解的是,当锁被占用的时候,Synchronized和ReentrantLock的区别是不大的。原因如下:
- Synchronized可以通过关联的Moniter对象中的Owner发现锁已经被占用,然后直接阻塞当前线程
- ReentrantLock通过锁状态state可以发现锁被占用,然后直接调用Unsafe.park()阻塞线程
当锁没有被占用,也就是说Moniter对象中的Owner == null 时,并且ReentrantLock中的state为0时,就会体现出的差别。
-
Synchronized加锁操作底层依赖操作系统的pthread_mutex_lock实现,当多个线程同时调用这个函数的时候,会让每一个线程都切换到内核态,由内核协调哪个线程获取到锁,哪些线程无法获取到锁。获取锁成功的线程会被唤醒,获取锁失败的线程会被内核进行阻塞,线程阻塞才能释放CPU资源,执行完才会从内核态转换为用户态。
-
ReentrantLock在加锁时会调用Unsafe.compareAndSwapInt(),这是操作系统指令上实现的原子性cas,其中不涉及内核态与用户态的切换。换而言之,ReentrantLock在Java层面维护锁的状态。ReentrantLock阻塞操作使用的是Unsafe.park(),其依赖操作系统的pthread_mute_trylock实现(pthread_mutex_lock的非阻塞版本,当获取不到锁时会直接返回,最终阻塞线程调用的是pthread_cond_wait),当多个线程同时竞争时,获取锁成功的线程不需要切换到内核态,可以直接运行,而其他线程会进入到内核态进行阻塞。
值得注意的是:尽管 ReentrantLock 不需要切换到内核去维护锁状态,但是在线程需要阻塞的情况下,内核态/用户态的切换还是无法避免的(线程的唤醒和挂起依旧是需要由内核进行的)