本文主要是介绍【多线程】CAS的应用 | CAS的概念 | 实现原子类 | 实现自旋锁,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 一、CAS
- 1.什么是CAS
- 2.实现原子类
- 3.实现自旋锁
一、CAS
1.什么是CAS
Compare and swap 比较并交换。
比较交换的是 内存 和 寄存器
比如此时有一个内存 : M。 还有两个寄存器A,B
CAS ( M , A , B ) :如果M和A的值相同的话,就把M和B的值进行交换(交换的目的是为了把B赋值给M,M = B),同时整个操作返回 true。如果M和A的值不同的话,无事发生,同时整个操作返回false。
//伪代码:
boolean CAS(address, expectValue, swapValue) {if (&address == expectedValue) {&address = swapValue;return true;}return false;
}
- CAS其实是一个CPU指令,一个CPU指令,就可以完成上述比较交换的逻辑。
- 单个的CPU指令是原子的。所以可以使用CAS来完成一些操作来代替“加锁”。为编写线程安全的代码提供了线的思路,基于CAS实现线程安全的方式,也叫“无锁编程”。
优点:保证线程安全,同时避免发生阻塞。
缺点:代码可读性差,并且只能适用在特定场景,不如加锁普适。
CAS本质上是CPU提供的指令,被操作系统封装后,提供成为APi。又再次被JVM进行封装,提供成API,最终由程序员调用方法实现操作。
2.实现原子类
之前int count++的操作,就不是原子的(load,add,save)
在Java标准库中,提供了原子类,例如AtomicInteger,基于CAS的方式对int进行封装,++就封装成原子操作了。
public static AtomicInteger count = new AtomicInteger(0);//对int进行原子化包装//0作为构造方法的参数public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(()->{for (int i = 0; i < 50000; i++) {count.getAndIncrement();//count++; 后置++
// count.incrementAndGet();//++count 前置++
// count.getAndDecrement();//count--; 后置--;
// count.decrementAndGet();//--count 前置--}});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);}
在Java中,有些操作是偏底层的操作,在使用时有着更多的注意事项,稍有不慎就容易写出问题。这些操作就同一放在unsafe这个类中
//伪代码
class AtomicInteger {private int value;//相当于内存public int getAndIncrement() {int oldValue = value;//oldValue相当于寄存器 while ( CAS(value, oldValue, oldValue+1) != true) {//CAS(内存,寄存器A,寄存器B)oldValue = value;}return oldValue;}
}
寄存器A先保存当前内存的值,CAS来判断此时有没有被穿插执行。如果没有穿插执行,内存的值等于寄存器A的值,把寄存器B(B执行+1操作)的值赋值给内存,同时CAS返回的是true,循环不成立,返回是是自增前的值。
如果被穿插执行了,CAS中,内存的值不等于寄存器A的值,CAS直接返回false.此时循环成立,将被穿插执行过的内存值,重新赋值给寄存器A,也就是说要再读一遍内存。再次执行CAS。
线程不安全的本质是进行操作的过程中,穿插执行。
1.加锁是通过阻塞的方式,避免穿插。
2.CAS是通过重试的方式,避免穿插。如果值和之前不相等了,重新更新一下,在进行操作。
3.实现自旋锁
//伪代码:
public class SpinLock {private Thread owner = null;//记录当前这个锁被哪个线程获取到。 null表示未加锁public void lock(){//加锁// 通过 CAS 看当前锁是否被某个线程持有. // 如果这个锁已经被别的线程持有, 那么就自旋等待. // 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程. while(!CAS(this.owner, null, Thread.currentThread())){}}public void unlock (){//解锁this.owner = null;}
}
- while循环中,CAS判断当前owner是不是未加锁状态。如果是未加锁的,就把owner赋值成 Thread.currentThread())(调用lock方法的线程引用)进行加锁。如果已经被加锁了,CAS返回false,取反之后,while就会反复执行。一旦其他线程释放了这个锁,owner变成null时,因为while不停循环,就会立即拿到当前这把锁。
缺点:while循环在忙等的过程中,不断消耗CPU资源。如果加锁的概率不大,付出的CPU资源就是值得的。如果当前的锁冲突很激烈,可能会拿不到,此时自旋锁就不适用了。
点击移步博客主页,欢迎光临~
这篇关于【多线程】CAS的应用 | CAS的概念 | 实现原子类 | 实现自旋锁的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!