Java EE 多线程之 synchronized 原理

1. synchronized 和 系统原生锁

在上一篇锁策略之后,我们大概了解到了六种锁
那么我们最为熟悉的 synchronized 锁 和 系统自带的锁有什么区别呢?

synchronized:

  • 乐观锁 / 悲观锁自适应
  • 轻量级锁 / 重量级锁自适应
  • 自旋锁 / 挂起等待锁自适应
  • 不是读写锁
  • 非公平锁
  • 可重入锁

系统原生的锁(Linex 提供的 mutex锁):

  • 悲观锁
  • 重量级锁
  • 挂起等待锁
  • 不是读写锁
  • 非公平锁
  • 不可重入锁

这里我们就来看一下 synchronized 是如何加锁的,是如何自适应的

2. synchronized 的加锁过程

当线程执行到 synchronized 的时候,如果这个对象当前处于未加锁的状态,就会经历以下过程

2.1 偏向锁阶段(假设没有线程来竞争)

核心思想是“懒汉模式”,能不加锁就不加锁,能晚加锁就晚加锁
所谓的偏向锁,并非真的加锁了,而只是做了一个非常轻量的标记

这里的偏向锁只是做一个标记,没有真的加锁,所以也不会存在互斥
但是一旦有其他线程来竞争这个锁,那么就会在另一个线程之前,把这个锁获取到
这个时候就会从偏向锁升级为轻量级锁,也就真的加锁了,也就有了互斥
如果在偏向锁这个过程中,没有线程来竞争,整个过程中就把加锁这样的操作就完全省略了

也可以说非必要不加锁
在遇到竞争的情况下,偏向锁没有提高效率
但是如果在又没竞争的情况下,偏向锁就大幅度的提高了效率

2.2 轻量级锁阶段(假设有竞争但是不多)

此处就是通过“自旋锁”的方式来实现

优势:另外的线程把锁释放了,就会第一时间拿到锁
劣势:比较消耗 cpu

于此同时,synchronized 内部也会统计当前这个锁对象上,有多少个线程参与竞争
这里当发现参与竞争的线程比较多,就会进一步升级到重量级锁

对于自旋锁来说,如果同一个锁竞争者很多,大量的线程都在自旋,整体 cpu 的消耗会很大

2.3 重量级锁阶段

此时拿不到锁的线程就不会继续自旋课,而是进入“阻塞等待”,就会让让出 cpu
当当前线程释放锁的时候,就由系统随机唤醒一个线程来获取锁了

3. 其他优化策略

3.1 锁消除

锁消除也是一种编译器优化的手段
编译器会自动针对你当前写的加锁的代码,做出判断
如果编译器觉得这个场景不需要加锁,此时就会把你写的 synchronized 给优化掉

就比如我们以前所学的 StringBuilder 和 StringBuffer
StringBuilder 不带 synchronized,StringBuffer 带有synchronized
如果在单个线程中使用 StringBuffer,此时编译器就会自动把 synchronized 给优化掉

但是,编译器只会在自己非常有把握的时候,才会进行锁消除,触发的概率不高


这里我们要区分锁消除和偏向锁的问题

偏向锁,是运行时发生的,运行过程中多线程的调度情况不同,这个线程的锁可能有人竞争,也有可能没人竞争
而锁消除,是在编译阶段,编译阶段无法判断这个锁是否是必要的,只能保守的保留锁

3.2 锁粗化

这里说到锁粗化,我们先看什么是锁的粒度

synchronized 里面,代码越多,就认为锁的粒度越粗;代码越少,锁的粒度越细

粒度细的时候,能够并发执行的逻辑更多,更有利于利用多核 cpu 资源
但是,如果粒度细的锁,被反复进行加锁劫夺,可能实际效果还不如粒度粗的锁(涉及到反复的锁竞争)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

柒柒要开心

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

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

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

打赏作者

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

抵扣说明:

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

余额充值