synchronized

synchronized

一、使用方式

  1. 修饰静态方法:锁对象为当前类对象
  • public static synchronized void method() {}
  1. 修改一般方法:锁对象为当前对象
  • public synchronized void method() {}
  1. 修改代码块:锁对象为传入的对象
  • synchronized (Lock.class) {}

二、对象组成

对象头

  1. 类型指针:用于确定是哪个类的实例
  2. MarkWord:包含HashCode,偏向标志、偏向线程ID、锁标志等信息

image

实例数据

  • 对象存储的信息,定义着各类型字段的内容

对齐填充

  • 对象的大小为8字节的整数倍

三、synchronized的优化

  • 无锁->偏向锁->轻量级锁->重量级锁

偏向锁

  1. 偏向锁适合一个线程进行访问,当线程获取锁时,使用CAS修改锁对象的MarkWord的偏向ID为当前线程的ID,每次只要比较偏向ID和线程的ID是否相同,相同也获取锁执行同步代码,不相同则判断是否可偏向,可偏向则CAS修改偏向线程ID,不可偏向说明产生竞争会进行锁撤销。
  2. 偏向锁释放时,不会重置偏向ID
  3. 批量重偏向:当一定时间内前偏向锁的锁撤销达到阈值时(默认为20次),JVM会认为之前的ID不适合作为偏向ID,此时可以进行重新偏向。每一个class都维护了偏向锁撤销计数器,当处于偏向状态时,MarkWord会有epoch属性,当创建class实例时,epoch会赋值给实例对象。当前偏向锁撤销时,class的epoch会加1,接着遍历所有当前存活的线程的栈,找到该 class 所有正处于偏向锁状态的锁实例对象,将其 epoch 值修改为新值。而没有被线程持有的其他class对象的epoch值比class小1。
  4. 批量锁撤销:一定时间内偏向锁撤销次数达到阈值(默认是40次),JVM会认为有多个线程竞争,此时不适合采用偏向锁,因为锁撤销也是要消耗性能的,JVM会关闭偏向锁,直接使用轻量级锁

轻量级锁

  • 轻量级锁适合多线程交替进行
  1. MarkWord为无锁状态,线程会在当前帧栈找一个空闲的锁记录空间,锁记录空间的displaced_header会存储MarkWord的内容,obj属性会指向当前锁对象,CAS修改当前锁对象的MarkWord指向锁记录空间的指针,成功则获取轻量级锁,失败则锁撤销升级为重量级锁。
    如果MarkWord不为无锁状态,判断当前线程拥有锁,如果已经拥有则锁重入,找到一个空闲的锁记录空间,displaced_header存入null。如果没有拥有锁则产生竞争,升级为重量级锁。
  2. 轻量级锁释放时,会判断displaced_header的内容,如果为null说明锁重入,不会真正释放锁。释放时,会把obj置为null,CAS将displaced_header 还原为锁对象的MarkWord

重量级锁

  • 重量级锁,底层采用互斥锁,会造成用户态和内核态的切换,所以很耗性能。修饰代码块时,会涉及到monitorenter和monitorexit字节码操作,操作方法时判断标志位ACC_SYNCHRIONIZED,本质是相同的。
  1. 重量级锁会创建一个ObjectMonitor对象(底层是C++),会将MarkWord的修改为指向ObjectMonitor的指针,当时锁标志置为10。
  2. 加锁失败时,会自旋(同步代码往往执行很快,自旋提高效率)。自旋一定程度没有获取锁那么会挂起,将线程存入ObjectMonitor的cxq队列中
  3. 解锁后会唤醒阻塞队列其他节点,其他节点获取锁会将自己从cxq(EntryList)队列移除,当EntryList队列为空时,cxq会将节点移到到该队列中

四、synchronized如何保证可见性、有序性、原子性

可见性

  • 加锁对共享变量操作后,会在解锁前,将变量刷入主内存

有序性

  • 遵循as-if-serial语言,单线程中,不管程序怎么重排序,最终的结果都是一样的,synchronized只能有一个线程获取锁,可以看出单线程

原子性

  • 只能有一个线程持有锁,保证了原子性

五、synchronized和Lock的区别

  1. synchronized是JVM层面,Lock是jdk提供的API
  2. synchronized会自动释放锁,Lock需要自己释放锁,一般在finally中释放
  3. synchronized是非公平锁,Lock可以定义为非公平和公平锁
  4. synchronized不可中断,只能等待释放了才能获取,ReetrantLock可以立即返回是否获取成功,有中断、超时等功能。synchronized的notify和notifyAll只能随机唤醒某个或者所有,ReetrantLock可以唤醒某个指定队列。
  5. 性能差不多

六、synchronized和Volatile的区别

可见性

  • synchronized解锁前将变量刷入内存,Volatile使用缓存一致性协议保证可见性

有序性

  • synchronized遵循as-if-serial语义,Volatile采用内存屏障

原子性

  • synchronized只能有一个线程持有锁,保证了原子性。Volatile不保证原子性。

七、sleep和wait的区别

  1. sleep是Thread类的方法,wait是每个类都拥有的方法。
  2. wait需要配合synchronized使用,先持有锁才能释放锁。
  3. sleep会释放cpu资源,wait除了释放cpu资源还会释放锁。

八、synchronize 底层维护了几个列表存放被阻塞的线程?

_cxq

  • 当线程获取锁失败后,会加入_cxq队列,_cxq的线程会在某一时刻,转移到_EntryLsit队列中

_EntryLsit

  • 当持有锁的线程释放锁后,_EntryList 链表头结点的线程会被唤醒,该线程称为 successor(假定继承者),然后该线程会尝试抢占锁

_WaitSet

  • 调用wait方法的线程会放入WaitSet队列,等待唤醒后加入_cxq或者_EntryLsit队列,默认放入_cxq队列头部
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值