当前此处介绍的这些所策略不仅仅局限于 Java,任何与锁相关的特性都与这些相关。
这里要注意的是,此处说的锁并不是单指一把锁,而是一类锁。
总的来讲,就是两者对锁的竞争激烈程度的认知不同。
synchronized 既是一个悲观锁,又是一个乐观锁。
默认是乐观锁,但是发现所竞争比较激烈时,就会变成悲观锁。
这里说明一个比较常见的情况(多数情况下):
多数情况下,乐观锁,是一个轻量级锁。
悲观锁,是一个重量级锁。
synchronized 既是一个轻量级锁,又是一个重量级锁。
默认是轻量级锁,当锁竞争比较激烈时,就会转换为重量级锁。
首先说明一下这两类锁的特性;
自旋锁,是一种典型的轻量级锁。
挂起等待锁,是一种典型的重量级锁。
下面我通过举例来解释两类锁的特点:
首先设想一个场景,这里我们邀请三为人选,分别是: 喜羊羊,美羊羊,沸羊羊。
此时,沸羊羊 向 美羊羊 表白(尝试对美羊羊加锁),但是,美羊羊说,沸羊羊你是个好人,但是我有男朋友了(说明此时 喜羊羊 对美羊羊已经加锁)
呢么,沸羊羊想要上位,就只能等待(锁释放),于是有下面两种。
synchronized 这里的轻量级锁,是基于自旋锁的形式实现。
synchronized 这里的重量级锁,是基于挂起等待锁实现的。
synchronized 不是读写锁
在这里,我们将公平定义为先来后到
同样,这里可以设定一个场景,此时假设美羊羊分手了。
公平锁: 当美羊羊分手后,由等待队列中最早的舔狗上位。(阻塞队列中的元素根据顺序依次获取锁)
synchronized 是非公平锁。
CAS:全称为Compare and swap,字面意思: 比较并交换
假设内存中的数据 V,旧的预期值 A,需要修改的新值 B。
如图所示:
这里需要注意的是,CAS 的这个过程,并非是一段代码实现,而是通过 一条 CPU 指令实现。
也就是说 CAS 操作是原子性的,这样就可以在一定程度上回避线程安全问题。
因为是原子类,真实的 CAS 是一个原子硬件指令来完成的,实现的是 i++ 这样的操作,这里只能使用伪代码来辅助理解,如图:
如上图所示,设定两个线程分别实现自增。
此时假设线程1 优先抢占CPU
此时,线程2进入 CPU
最后返回线程各自的 oldvalue 即可!
总的来说,CAS 就是 CPU 提供给我们的一种特殊指令,通过这个指令,可以再一定程度上处理线程安全问题。
什么是 ABA 问题
我们已知,CAS 在运行中,就是检查 value 和 oldvalue 是否一致。如果一致,就视为 value 的值中途没有被修改过,所以下一步交换没有问题。
需要注意的是这里的 “一致”,可能是没有改过,也可能是 改过,但是又还原回来了。
通俗来讲,就是,我买了个手机,这个手机可能是新机,也可能是翻新机。这里我们不专业,无法区分!
ABA 这样的问题,在大部分情况下影响都不大。但是,仍然有极端情况不容忽视,问题如下:
假设到 ATM 上取钱,在取钱的时候,按下取钱键的一瞬间,机器故障卡了,此时我又不耐烦的多按了几下,此时就可能产生 bug,造成重复扣款的情况,如图:
解决方案
给要修改的值, 引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期.
目前,我们知道 synchronized 的最基本用法是,两个线程针对同一个对象加锁,会产生阻塞等待。
对于 synchronized 内部,其实还有很多优化机制,存在的目的就是为了让锁更高效。
呢么有个问题,尽然能实现锁升级,呢么可不可以降级?
答案是不行。 在 JVM 的主流实现中,只有锁升级,没有锁降级。只要是锁对象,一旦被升级,就不能再回头了。
对于这里的内容,本人整理的十分有限,不足的地方还希望大家多多指点。