并发编程面试题之synchronized实现原理
并发编程面试文章地址链接
内容 | 博客链接 |
---|---|
并发编程面试题之常见面试题 | https://blog.****.net/weixin_38251871/article/details/104658674 |
并发编程面试题之 volatile 关键字 |
https://blog.****.net/weixin_38251871/article/details/104667384 |
并发编程面试题之 CAS
|
https://blog.****.net/weixin_38251871/article/details/104667406 |
并发编程面试题之锁 | https://blog.****.net/weixin_38251871/article/details/104667392 |
并发编程面试题之阻塞队列 | 待完成… |
并发编程面试题之 AQS
|
待完成… |
并发编程面试题之线程池 | https://blog.****.net/weixin_38251871/article/details/104675416 |
并发编程面试题之 synchronized 和 ReentrantLock 的区别 |
https://blog.****.net/weixin_38251871/article/details/104667532 |
并发编程面试题之 CyclicBarrier、CountDownLatch、Semaphore
|
https://blog.****.net/weixin_38251871/article/details/104677582 |
并发编程面试题之 ConcurrentHashMap
|
https://blog.****.net/weixin_38251871/article/details/104667433 |
synchronized 核心组件
-
Wait Set:
放置调用Object#wait()
方法后被阻塞的线程 -
Contention List:
竞争队列,所有请求锁的线程先放置在这个竞争队列中 -
Entry List:
竞争队列中有资格成为候选资源的线程被移动到Entry List
中 -
OnDeck:
无论什么时候, 最多只有一个线程正在竞争锁资源,该线程就叫OnDeck
-
Owner:
当前已经获取到锁资源的线程叫做Owner
-
!Owner:
当前释放锁的线程
synchronized 底层实现原理
-
JVM
每次从队列的尾部取出一个数据用于锁竞争候选者(OnDeck)
,但是在并发的情况下,Contention List
会被大量的并发线程进行CAS
访问,为了降低对尾部元素的竞争,JVM
会将一部分线程移动到Entry List
中作为候选竞争线程 -
Owner
线程会在unlock
时将Contention List
中的部分线程迁移到Entry List
中,并指定Entry List
中的某个线程为OnDeck
线程 -
Owner·
线程并不直接把锁传递给OnDeck
线程, 而是把锁竞争的权利交给OnDeck
,OnDeck
需要重新竞争锁。这样虽然牺牲了一些公平性, 但是能极大的提升系统的吞吐量, 在JVM
中, 这种行为称之为竞争切换
-
OnDeck
线程获取到锁资源后会变成Owner
线程, 而没有获取到锁的线程仍然停留在Entry List
中, 如果Owner
线程被Object#wait()
方法阻塞,则转移到Wait Set
队列中, 直到某个时刻通过Object#notify()/Object#notifyAll()
方法唤醒, 会重新进入到Entry List
中 - 处于
Contention List、Entry List、Wait Set
中的线程都处于阻塞状态, 该阻塞是由操作系统来完成的 -
synchronized
是非公平锁,synchronized
在线程进入Contention List
的时候, 等待的线程会先尝试自旋获取锁, 如果获取不到锁就进入Contention List
中, 这明显对于已经进入到队列的线程是不公平的, 还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占OnDeck
线程的锁资源 - 每个对象都有一个
monitor
对象, 加锁就是在竞争monitor
对象, 代码块加锁就是在前后分别加上monitorenter 与 monitorexit
指令来实现的, 方法加锁就是通过一个标记位来判断的 -
synchronized
是一个重量级的操作, 需要调用操作系统相关接口, 性能低效. 有可能给线程加锁的时间比操作程序的时间更多 - 值得庆幸的是, 在
JDK 1.6
之后,synchronized
进行了很多的优化,适应自旋、锁消除、锁粗化、轻量级锁、偏向锁等
, 在效率上有本质上的提高, 在JDK 1.7/JDK 1.8
中都对synchronized
关键字的实现机制做了优化, 都是在对象头中由标记位, 不需要经过操作系统加锁 - 锁升级可以从
偏向锁 --> 轻量级锁 --> 重量级锁
, 这种升级过程叫锁膨胀 -
JDK 1.6
默认开启偏向锁和轻量级锁, 可以通过-XX:-UseBiasedLocking
来禁用偏向锁