Synchronized的前世今生

目录

1.对象头以及markword

1.1.普通同步方法

1.2.同步代码块

2.重量级锁

3.偏向锁

4.轻量级锁

5.锁优化


java1.5之前Synchronized是一个重量级锁

java1.6有了各种优化,锁的四种分类:无锁-》偏向锁-》轻量级锁-》重量级锁

1.对象头以及markword

对象包括:

  • 对象头:markword(哈希码,GC分代年龄,锁状态标识,线程持有锁,偏向线程ID,偏向时间戳),类型指针,如果是数组,还包括数组长度
  • 实例数据
  • 对齐填充(非必须)

三个用法:

  • 普通同步方法:锁的是当前实例对象
  • 静态同步方法:锁的是当前类的class对象
  • 同步代码块:锁的是Synchronized括号里的对象

无锁-》偏向锁-》轻量级锁-》重量级锁

具体使用哪种锁,要看当前竞争激烈程度

无竞争:会使用偏向锁

轻量竞争:会由偏向锁升级成轻量级锁

重度竞争:会由轻量级锁升级成重量级锁

1.1.普通同步方法

线程栈通过指令去调用在堆里的实例对象,去检查在方法区中的访问标志,也就是大名鼎鼎的ACC_SYNCHRONIZED是否被设置,怎么检查的呢?

1.其实就是检查mark word的锁标志位是不是01

2.把该对象的对象头中的mark word拷贝到自己的执行线程栈中(displaced mark word)

3.通过displaced mark word中指向的重量级锁的指针,找到Monitor对象的起始地址

之后大家就熟悉了,获取Monitor对象,获取成功之后才能执行方法体,其他线程无法获得这个monitor对象了。

1.2.同步代码块

利用了monitorEnter和monitorExit这两个字节码指令,它们分别位于同步代码块的开始和结束位置。

当jvm执行到monitorEnter指令的时候,当前线程试图去获取这个对象的monitor所有权,如果成功,锁计数器+1;当执行到monitorExit指令的时候,锁计数器-1,锁就释放了。如果获取monitor对象失败,线程进入到阻塞状态,直到其他线程释放锁。

java -verbose java文件

就是可以分别看到ACC_SYNCHRONIZED,monitorEnter和monitorExit这是三个字节码指令了。

2.重量级锁

获取对象的同步监视器Monitor,这是JVM层的操作,底层使用了操作系统的互斥量

(Mutex Lock),而互斥量的本身就存在用户态到核心态的切换,这种行为本身代价就很高,之后的改进其目的都是为了减少这种情况的发生。

3.偏向锁

大多数情况下,锁不存在多线程竞争,总是由同一个线程多次获得,偏向锁的目的就是降低多次加锁解锁的开销。

偏向锁会偏向第一个获得它的线程,如果在接下来的执行过程中,该锁没有被其他线程获取,那么持有该锁的线程永远不需要同步。

适用场景:只适用于一个线程访问同步块的场景,当有另一个线程去尝试获得锁,偏向锁就结束了,该对象被恢复到无锁或者轻量级锁状态

优缺点:

加锁和解锁不需要额外消耗;

多线程竞争下,会带来额外锁撤销的消耗,这种情况下建议关闭偏向锁

(--XX:-UseBiaseLocking来禁用偏向锁)。

4.轻量级锁

当出现多个线程进行竞争的时候,或出现锁升级,由原来的的无锁或者偏向锁升级成轻量级锁。

轻量级锁所适应的场景:线程交替执行同步方法或者同步块的时候,如果存在同一时间访问同一锁的情况下,会导致锁进一步升级成重量级锁。

前面流程跟偏向锁一样,

  1. 该对象的对象头中的mark word拷贝到事先在执行线程栈中建立好的锁记录空间里(Lock Record),这种拷贝记录被称为displaced mark word。
  2. CAS操作尝试将对象的Mark Word更新成指向Lock Record,并修改Lock Record里的owner指针指向object mark word
  3. 如果更新成功,则获取对象锁成功,锁标志被设置成“00”,表示该对象处于轻量级锁状态
  4. 如果更新失败,检查当前对象的mark word是否指向当前线程,如果是就表示已经获取了锁,可以直接进入同步块执行,否则就说明竞争严重,锁要升级成重量级锁,锁标志更新成“10”,Mark word中存储就是互斥量的指针,后续等待锁的线程都得进入线程阻塞状态。当前线程便尝试使用自旋锁来获取锁,自旋就是为了不让线程阻塞,而采取的去获得锁的过程。

轻量级解锁过程:通过CAS把复制过来的displaced mark word对象替换成当前的mark word,替换成功,则整个同步过程就完成了;如果替换失败,说明有其他线程去尝试获取锁,就要在释放锁的同时,唤起被挂起的线程,线程的挂起和阻塞都会造成线程切换,增加付出时间代价的成本。

5.锁优化

适应性自旋:获取轻量级锁失败的过程中执行CAS操作失败,需要自旋获取重量级锁,自旋需要消耗CPU,白白浪费CPU资源,需要指定自旋次数,JDK更聪明采用适应性自旋,如果线程自旋成功了,自旋次数就会增加,否则就减少自旋次数。

锁合并(粗化):将多次加锁解锁的操作合并,例如stringbuffer中的append方法,只需要第一次加锁,最后一次解锁即可。

锁消除:删除不必要的加锁操作,根据代码逃逸技术,认为这段代码是线程安全的,就不需要加锁操。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

彼岸花@开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值