目录
锁策略:
乐观锁和悲观锁:
侧重于加锁态度
乐观锁:预期锁竞争不激烈,选择不加锁或者少加锁。
悲观锁:在预期锁竞争激烈,加锁。
轻量级锁和重量级锁:
侧重于加锁过程
轻量级锁:过程简单,消耗资源少,用户态加锁操作就可以完成(java层面)。(乐观锁属于轻量级锁的一种)
重量级锁:过程复杂,消耗资源多,内核态加锁(系统内核)。(悲观锁属于重量级锁的一种)
自旋锁和挂起等待锁:
自旋锁:一直在检查锁资源是否释放,一旦锁资源释放马上获取锁资源。(用户态操作,轻量级,能及时获取锁资源,但是一直检查,浪费资源)
挂起等待锁:不主动检查锁资源,由系统调度分配锁资源。(无法第一时间获取锁资源)
读写锁和普通互斥锁:
读写锁:一个线程对于数据的访问, 主要存在两种操作: 读数据 和 写数据.
普通互斥锁:竞争关系,只能一个线程获取锁资源,其他线程要等锁资源被释放。
公平锁和非公平锁:
公平锁:先来后到,先排队的先得,后排队的后得锁资源(现实中这只是一种追求无法做到绝对的公平)
非公平锁:谁先抢到做资源,就是谁的。
公平锁需要消耗资源去专门实现,效率也会有折扣,而非公平锁没有那么多的消耗。java中很多地方默认都是使用的非公平锁。
可重入锁和不可重入锁:
可重入锁:可以对同一把锁加锁多次而不造成死锁,同样解锁的时候也要多次解锁。
不可重入锁:对一把锁连续加锁会造成死锁。
对比synchronized:
既是乐观锁又是悲观锁
既是轻量级锁又是重量级锁
既是自旋锁又是挂起等待锁
是普通互斥锁,非公平锁,可重入锁
在锁竞争不激烈的时候synchronized是轻量级锁乐观锁自旋锁,竞争激烈的时候自动升级为重量级锁悲观锁挂起等待锁
什么是CAS:
CAS(Compare and Swap比较并交换)
伪代码:
boolean CAS(address, expectValue, swapValue) {
if (&address == expectedValue) {
&address = swapValue;
return true;
}
return false;
}
即:用一个预期值与内存中的值相比较,若相等则交换否则不操作
CAS图解:![](https://i-blog.csdnimg.cn/blog_migrate/7802b83d9a975c0418bb57f322ba6afb.png)
线程一和线程二均先读取了主内存的值,假设线程一要对值进行修改,则先比较主内存的值与自己先前加载的值是否相等,相等则加一操作,再把值写回主内存。
这时候如果线程二要对值进行修改,则先比较主内存和自己先前加载的值,发现不相同,线程二会重新从主内存加载值
这时候线程二要再修改,就先和主内存的值比较,相等则修改然后把新修改的值写回主内存
CAS以这种每次都比较原值与主内存值的方式保证了原子性 ,自旋则是在CAS外面包了一层循环让CAS在循环中执行。
自旋锁伪代码:
public class SpinLock {
private Thread owner = null;
public void lock(){
// 通过 CAS 看当前锁是否被某个线程持有.
// 如果这个锁已经被别的线程持有, 那么就自旋等待.
// 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.
while(!CAS(this.owner, null, Thread.currentThread())){
}
}
public void unlock (){
this.owner = null;
}
}
CAS的ABA问题(重点):
假设有两个线程t1和t2,初始值为0,后面如果t1想修改0 的值为1,则要先进行比较,发现相等修改为1,但是在t1执行这个问题的时候可能t2已经执行过操作把0改成了2又把2改回了0,也就是说t1在执行操作的时候,无法判断当前值是否是已经被修改多次的值。
ABA问题解决办法:
可以选择加一个标志位,记录值是否被修改。例如软件更新时都会有一个版本号。