CAS比较交换的知识、ABA问题、锁升级的流程

2022-07-26 13:49:32

目录

CAS : Compare and Swap比较交换

1. 使用CAS实现了原子类

假设两个线程同时调用 getAndIncrement

2. 使用CAS来实现自旋锁

3. CAS引发的ABA问题

synchronized关键字背后的锁升级的流程


CAS : Compare and Swap比较交换

CAS是乐观锁的一种实现,程序并不会阻塞,不断重试。
应用1. 原子类,在java.util.concurrent(juc包、并发工具包),

如:原子类,线程安全集合,ConcurrentHashMap,CopyOnWriteArrayList,都在juc包下。

int i = 0; i++,i--都是非原子性操作,多线程并发会有线程安全问题。

使用原子类保证他的线程安全性
AtomicInteger i = new Atomiclnteger();//无参构造就是默认为0AtomicIntegeri = new Atomiclnteger(10);1/默认就从10开始计数

不会真正阻塞线程,不断尝试更新
假设当前主内存中的值为V,工作内存中的值为A,当前线程要修改的值为B

CAS的操作流程如下:
1.比较内存中的值V和当前工作内存的值A是否相等
2.若相等,可以认为当前主内存的值没有被修改,当前线程就将值B写回主内存
若不相等,说明当前线程的值A已经过时了(主内存已经发生了变化),将当前主内存的最新值V保存到当前工作内存中,此时无法将B写回主内存,继续循环。

当多个线程同时对某个资源进行CAS操作,只能有一个线程操作成功,但是并不会阻塞其他线程,其他线
程只会收到操作失败的信号。
CAS 可以视为是一种乐观锁. (或者可以理解成 CAS 是乐观锁的一种实现方式)

CAS 是怎么实现的
针对不同的操作系统,JVM 用到了不同的 CAS 实现原理,简单来讲:
java 的 CAS 利用的的是 unsafe 这个类提供的 CAS 操作;
unsafe 的 CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg;
Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子性。
简而言之,是因为硬件予以了支持,软件层面才能做到。

1. 使用CAS实现了原子类

java.util.concurrent.atomic包中的所有类都是线程安全的原子类

i++线程不安全。

public class AtomicTest {
    class Counter {
        AtomicInteger count = new AtomicInteger();
    }

    public static void main(String[] args) {
        // 0
        AtomicInteger count = new AtomicInteger();
        // 基于整型的原子类
        // 等同于 ++i 1
        System.out.println(count.incrementAndGet());
        // 等同于 i++ 1
        System.out.println(count.getAndIncrement());
        // -- i 1
        System.out.println(count.decrementAndGet());
        // i -- 1
        System.out.println(count.getAndDecrement());
        // 0
        System.out.println(count.get());
    }
}


count.incrementAndGet(); 使用CAS机制保证原子性

public class AtomicTest {
    static class Counter {
        AtomicInteger count = new AtomicInteger();

        void increase() {
            // i++
            count.incrementAndGet();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter.increase();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.count.get());
    }
}

假设两个线程同时调用 getAndIncrement

 原子类伪代码

class AtomicInteger {
    private int value;
    public int getAndIncrement() {
        int oldValue = value;
        while ( CAS(value, oldValue, oldValue+1) != true) {
            oldValue = value;
       }
        return oldValue;
    }
}

CAS 的工作流程伪代码

boolean CAS(address, expectValue, swapValue) {
 if (&address == expectedValue) {
   &address = swapValue;
        return true;
   }
    return false;
}

2. 使用CAS来实现自旋锁

乐观锁的一种实现
自旋锁指的是获取锁失败的线程不进入阻塞态,而是在CPU上空转(线程不让出CPU,而是跑一些无用指令),不断查询当前锁的状态。

例如:等红绿灯,脚踩在刹车上但是车不熄火。

参数解释

3. CAS引发的ABA问题

有两个线程t1和t2,同时修改共享变量num,初始num == A。
正常情况下,只有一个线程会将num值改为正确值,另一个线程在修改时num != A,另一个线程的工作内存的值已经过期了,因此无法修改。

ABA问题

例子

解决ABA问题

引入版本号
只有当CAS中,V == A时才会用到版本号,来判断当前主内存的V是否被其他线程反复修改过。
有了版本号之后,在线程1看到V == A,准备修改值之前,判断其他线程是否对主内存有过操作,有操作则不修改。

synchronized关键字背后的锁升级的流程

看起来都用的synchronized关键字,背后到底是什么锁,是JVM来进行处理的。

JVM会根据竞争的激烈程度动态选择锁的实现。

  • 作者:瘦皮猴117
  • 原文链接:https://blog.csdn.net/XHT117/article/details/125777003
    更新时间:2022-07-26 13:49:32