synchronized
一、使用方式
- 修饰静态方法:锁对象为当前类对象
public static synchronized void method() {}
- 修改一般方法:锁对象为当前对象
public synchronized void method() {}
- 修改代码块:锁对象为传入的对象
synchronized (Lock.class) {}
二、对象组成
对象头
- 类型指针:用于确定是哪个类的实例
- MarkWord:包含HashCode,偏向标志、偏向线程ID、锁标志等信息
实例数据
- 对象存储的信息,定义着各类型字段的内容
对齐填充
- 对象的大小为8字节的整数倍
三、synchronized的优化
- 无锁->偏向锁->轻量级锁->重量级锁
偏向锁
- 偏向锁适合一个线程进行访问,当线程获取锁时,使用CAS修改锁对象的MarkWord的偏向ID为当前线程的ID,每次只要比较偏向ID和线程的ID是否相同,相同也获取锁执行同步代码,不相同则判断是否可偏向,可偏向则CAS修改偏向线程ID,不可偏向说明产生竞争会进行锁撤销。
- 偏向锁释放时,不会重置偏向ID
- 批量重偏向:当一定时间内前偏向锁的锁撤销达到阈值时(默认为20次),JVM会认为之前的ID不适合作为偏向ID,此时可以进行重新偏向。每一个class都维护了偏向锁撤销计数器,当处于偏向状态时,MarkWord会有epoch属性,当创建class实例时,epoch会赋值给实例对象。当前偏向锁撤销时,class的epoch会加1,接着遍历所有当前存活的线程的栈,找到该 class 所有正处于偏向锁状态的锁实例对象,将其 epoch 值修改为新值。而没有被线程持有的其他class对象的epoch值比class小1。
- 批量锁撤销:一定时间内偏向锁撤销次数达到阈值(默认是40次),JVM会认为有多个线程竞争,此时不适合采用偏向锁,因为锁撤销也是要消耗性能的,JVM会关闭偏向锁,直接使用轻量级锁
轻量级锁
- 轻量级锁适合多线程交替进行
- MarkWord为无锁状态,线程会在当前帧栈找一个空闲的锁记录空间,锁记录空间的displaced_header会存储MarkWord的内容,obj属性会指向当前锁对象,CAS修改当前锁对象的MarkWord指向锁记录空间的指针,成功则获取轻量级锁,失败则锁撤销升级为重量级锁。
如果MarkWord不为无锁状态,判断当前线程拥有锁,如果已经拥有则锁重入,找到一个空闲的锁记录空间,displaced_header存入null。如果没有拥有锁则产生竞争,升级为重量级锁。 - 轻量级锁释放时,会判断displaced_header的内容,如果为null说明锁重入,不会真正释放锁。释放时,会把obj置为null,CAS将displaced_header 还原为锁对象的MarkWord
重量级锁
- 重量级锁,底层采用互斥锁,会造成用户态和内核态的切换,所以很耗性能。修饰代码块时,会涉及到monitorenter和monitorexit字节码操作,操作方法时判断标志位ACC_SYNCHRIONIZED,本质是相同的。
- 重量级锁会创建一个ObjectMonitor对象(底层是C++),会将MarkWord的修改为指向ObjectMonitor的指针,当时锁标志置为10。
- 加锁失败时,会自旋(同步代码往往执行很快,自旋提高效率)。自旋一定程度没有获取锁那么会挂起,将线程存入ObjectMonitor的cxq队列中
- 解锁后会唤醒阻塞队列其他节点,其他节点获取锁会将自己从cxq(EntryList)队列移除,当EntryList队列为空时,cxq会将节点移到到该队列中
四、synchronized如何保证可见性、有序性、原子性
可见性
- 加锁对共享变量操作后,会在解锁前,将变量刷入主内存
有序性
- 遵循as-if-serial语言,单线程中,不管程序怎么重排序,最终的结果都是一样的,synchronized只能有一个线程获取锁,可以看出单线程
原子性
- 只能有一个线程持有锁,保证了原子性
五、synchronized和Lock的区别
- synchronized是JVM层面,Lock是jdk提供的API
- synchronized会自动释放锁,Lock需要自己释放锁,一般在finally中释放
- synchronized是非公平锁,Lock可以定义为非公平和公平锁
- synchronized不可中断,只能等待释放了才能获取,ReetrantLock可以立即返回是否获取成功,有中断、超时等功能。synchronized的notify和notifyAll只能随机唤醒某个或者所有,ReetrantLock可以唤醒某个指定队列。
- 性能差不多
六、synchronized和Volatile的区别
可见性
- synchronized解锁前将变量刷入内存,Volatile使用缓存一致性协议保证可见性
有序性
- synchronized遵循as-if-serial语义,Volatile采用内存屏障
原子性
- synchronized只能有一个线程持有锁,保证了原子性。Volatile不保证原子性。
七、sleep和wait的区别
- sleep是Thread类的方法,wait是每个类都拥有的方法。
- wait需要配合synchronized使用,先持有锁才能释放锁。
- sleep会释放cpu资源,wait除了释放cpu资源还会释放锁。
八、synchronize 底层维护了几个列表存放被阻塞的线程?
_cxq
- 当线程获取锁失败后,会加入_cxq队列,_cxq的线程会在某一时刻,转移到_EntryLsit队列中
_EntryLsit
- 当持有锁的线程释放锁后,_EntryList 链表头结点的线程会被唤醒,该线程称为 successor(假定继承者),然后该线程会尝试抢占锁
_WaitSet
- 调用wait方法的线程会放入WaitSet队列,等待唤醒后加入_cxq或者_EntryLsit队列,默认放入_cxq队列头部