常见的锁策略
乐观锁&悲观锁
描述的是一种态度。
乐观锁:对运行环境持乐观的态度,刚开始不加锁,当有竞争的时候再去加锁;
悲观锁:对运行环境持悲观态度,刚开始就直接加锁。
轻量级锁&重量级锁
描述的是实现锁的过程,在实现锁的过程中,消耗的资源多不多。
轻量级锁:可以是纯用户态的锁,消耗的资源少。
重量级锁:可能会调用到系统的内核态,消耗的资源比较多。
读写锁&普通互斥锁
在现实中,并不是所有的锁都要互斥,互斥必然要消耗很多的资源。所以优化出了读写锁。
读锁:是一种共享锁,读与读可以同时拿到锁资源;
写锁:是一种排他锁,写与写、写与读、读与写不能同时存在。
自旋锁&挂起等待锁
自旋锁:不停的询问资源是否被释放,如果释放了,第一时间获取到锁资源。可以通过纯用户态实现。
挂起等待锁:等待通知后再去竞争锁,并不会第一时间获取锁到资源。
可重入锁&不可重入锁
可重入锁:对于同一个锁对象可以加多次锁;
不可重入锁:不能对同一个锁对象加多次锁。
公平锁&非公平锁
公平锁:先排队等待的线程先获取到锁资源;
非公平锁:没有先来后到,谁抢到是谁的。
CAS
CAS: 全称Compare and swap,字面意思:”比较并交换“。先来看一下CAS的伪代码:
address指的是内存地址,expectValue指的是期望值,swapValue指的是要交换的值。用期望值与内存中的值比较,如果相等,那么用swapValue覆盖内存中的值。如果期望值与内存中的值不等,什么也不做。
CAS实现原子类
JDK中提供了原子类比如:AtomicInteger。对比之前写过的两个线程个自增五万次的操作,可以发现不使用synchronized而使用CAS就可以得到预期结果,从硬件层面CAS就支持原子性,这是纯用户态的操作。
public class Demo01_CAS {
public static void main(String[] args) throws InterruptedException {
//原子整型
AtomicInteger atomicInteger= new AtomicInteger();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
//自增
atomicInteger.getAndIncrement();
}
});
t1.start();
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
//获取并自增
atomicInteger.getAndIncrement();
}
});
t2.start();
//等待两个线程执行完成
t1.join();
t2.join();
//打印结果
System.out.println(atomicInteger.get());
}
}
如果要对原子类中的value做自增,那么就要先把当前的value值取出来,就可以把当前value值当作后续CAS的操作。两个线程通过CAS同时对一个共享变量做自增,通过不停的自旋(while循环)检查预期值来保证线程安全。 while循环是在应用层执行的,也就是用户态,所以比内核态的锁效率要高很多。
🟢执行过程:
线程1将value从主内存加载到自己的工作内存中,然后线程2也将value加载到自己的工作内存中。线程1对value做ADD,然后线程2对value做ADD。此时线程1执行比较并交换指令(cmpxchg),比较自己工作内存中的预期值value=0和主内存中的value=0比较,此时相等,将新值value=1给到主内存,此时主内存中value=1;t2线程比较预期值value=0与主内存中的value=1,此时不等,将主内存中的value=1加载到t2的工作内存中作为预期值,ADD之后工作内存中value=2,此时t2的工作内存中的预期值value=1与主内存的value=1相等,比较并交换,将主内存中的value变为2。
CAS实现自旋锁
先来看一下自旋锁的伪代码:
CAS的ABA问题
有这样一个场景:当我把茶泡好之后就出门了,过十分钟我回来。
茶水的状态有两种情况:满的和不满。在满的情况下有两种可能:确实是没人喝过;别人喝了之后又给我添满了。虽然此时水的状态是满的,但这杯水已经不是我走的时候的那杯水了。这就是CSA中的ABA问题。A B A表示预期值的三个状态。在真实业务中,可能会造成影响。
解决ABA问题:
给预期值加一个版本号。在做CAS操作时同时更新预期值的版本号,版本号只增不减。
CAS总结
1.先获取预期值;
2.通过CAS指定完成比较并交换;
3.如果在CAS的过程中预期值与真实值不相等,就进入自旋;
4.ABA问题,主要给预期值加一个版本号,在比较的时候同时比较真实值和版本号。
继续加油~