一.常见的锁策略
锁策略不是指某一个具体的锁,所有的锁都可以往这些锁策略中套
1.悲观锁与乐观锁
预测所冲突的概率是否高,悲观锁为预测锁冲突的概率较高,乐观锁为预测锁冲突的概率更低。
2.重量级锁和轻量级锁
从加锁的开销角度判断,重量级锁与悲观锁相对应,轻量级锁与乐观锁相对应。(并不是一定的,大多数情况是这样的)
3.挂起等待锁和自旋锁
挂起等待锁,就是悲观锁/重量级锁的一种典型实现。
自旋锁,则是乐观锁/轻量级锁的一种典型实现。
自旋锁,会在不释放cpu资源的情况下一直检测目标锁是否被释放,一旦锁被释放,就立即有机会获得锁(需要消耗更多cpu资源,且要在所冲突不高的时候)
挂起等待锁 ,就是让出cpu资源,让cpu去做别的事情,等待通知后才会获得锁资源。(时效性低,需消耗更多时间)
4.公平锁和非公平锁
公平锁:先到先得,先阻塞等待的先得到锁,后来后得。
非公平锁:不按照先来后到的顺序,谁争抢到锁归谁(大部分情况下都是使用非公平锁,效率高)
5.可重入锁和不可重入锁
如果对一个线程,针对一把锁重复加锁两次,就有可能出现死锁。
如果把锁设定成**"可重入"**就可以避免死锁了
可重入锁:对同一个锁资源可以加多次锁
不可重入锁:不可以对同一个锁资源加多次锁
6.读写锁
注:synchronized并非是读写锁
所谓读写锁,就是把"加锁操作"分成两种情况:加读锁,加写锁
读锁:共享锁,读与读操作都能同时拿到锁资源
写锁:排它锁,读写,写读,写写不能同时拿到锁资源
二.synchronized原理
1.特征
1)既是乐观锁,又是悲观锁(自适应)
2)是轻量级锁,也是重量级锁(自适应)
3)不是读写锁
4)挂起等待锁和自旋锁(自适应)
5)是可重入锁
6)非公平锁(锁竞争)
2.锁升级
1)刚开始使用synchronized加锁,首先锁会处于"偏向锁"状态。
偏向锁:相当于一种标记,不是真正加锁(更为轻量高效)
2)遇到线程之间的锁竞争时,会升级到"轻量级锁"。
3)进一步统计锁竞争出现的频次,达到一定程度后,升级到"重量级锁"
无锁=》偏向锁=》轻量级锁=》重量级锁
上述锁升级的过程,主要是为了能够让synchronized这个锁更好的适应不同的场景,降低程序员负担。
注:上诉锁升级的过程是不可逆的
3.锁消除与锁粗化
1)锁消除
编译器会对所写的synchronized代码做出判定,判断这个地方是否确实需要加锁。
如果这个加锁是没有必要的,能够自动把synchronized给干掉。
2)锁粗化
也是一种编译器的优化策略
锁的粗细,是根据锁的粒度来判断的。
代码越多,则粒度越粗,
代码越少,则粒度越细。
锁粗化,就是把多个"细粒度"的锁合并为"粗粒度"的锁
三.CAS
1.概念
CAS全称"compare and swap",用于比较内存与cpu寄存器种的内容。如果发现相同,就进行交换(交换的是内存和另一个寄存器的值)
如有一个内存中的数据和寄存器1,寄存器2.
比较内存中的数据与寄存器1中的数据是否相等,如果相等,将内存和寄存器2的数据进行交换。
CAS的关键是通过一个cpu指令(原子的)完成了一系列操作,可为编写多线程代码带来新的思路
称为"无锁化编程"
2.使用场景
基于CAS实现"原子类"。
java
public class Dem1 {
private static AtomicInteger count = new AtomicInteger();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i <50000; i++) {
count.getAndIncrement();
}
});
Thread t2=new Thread(()->{
for (int i = 0; i < 50000; i++) {
count.getAndIncrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count="+count.get());
}
}
3.CAS工作原理
假如有两个线程t1,t2进行自增操作
线程t1先读取到数据,t2后读取到,但t2优先进行cas操作,value+1.
此时调度回t1进行cas操作,能够发现数据发生了变化(value!=oldvalue)。
因此,就能判断出其它线程趁着t1load时修改了value的值,此时便不会进行cas操作,而是再来一次load,确保寄存器中的值是正确的值,然后进行cas。
3.ABA问题
CAS之所以能保证线程安全,重要的一点在于通过CAS比较的过程中,来确认是否有其它线程插入进来执行(通过比较value和oldvalue)
此处是通过判定值是否相同,来判断是否有其它线程修改过。
但是值相同!=没有修改可能。
有可能存在另一个线程修改了值,又修改回去了。这就是ABA问题。
ABA问题产生的原因是因为value可增可减
引入"版本号",设置版本号只能增加,可以解决ABA问题