Java中CAS 基本实现原理 和 AQS 原理

一、前言
了解CAS,首先要清楚JUC,那么什么是JUC呢?JUC就是java.util.concurrent包的简称。它有核心就是CAS与AQS。CAS是java.util.concurrent.atomic包的基础,如AtomicInteger、AtomicBoolean、AtomicLong等等类都是基于CAS。

什么是CAS呢?全称Compare And Swap,比较并交换。CAS有三个操作数,内存值V,旧的预期值E,要修改的新值N。当且仅当预期值E和内存值V相同时,将内存值V修改为N,否则什么都不做。

 

 

二、实例

如果我们需要对一个数进行加法操作,应该怎样去实现呢?我们模拟多个线程情况下进行操作。

ThreadDemo.java 实现一个Runnable接口

package com.spring.security.test;
 
public class ThreadDemo implements Runnable {
    
    private int count = 0;
 
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            addCount();
        }
    }
    
    private void addCount() {
        count++;
    }
    
    public int getCount() {
        return count;
    }
}

 

ThreadTest.java 创建线程池,提交10个线程执行,预期结果应该是1000

 

package com.spring.security.test;
 
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class ThreadTest {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        ThreadDemo threadDemo = new ThreadDemo();
        for (int i = 0; i < 10; i++) {
            threadPool.submit(threadDemo);
        }
        threadPool.shutdown();
        System.out.println(threadDemo.getCount());
    }
}

 

 运行结果:874 或其他,与预期结果不符合。

 执行出来的结果并不是想象中的结果。这是为什么呢?这跟线程的执行过程有关。

 

 

 

 

所以我们需要在改变count,将值从高速缓冲区刷新到主内存后,让其他线程重新读取主内存中的值到自己的工作内存。

此时可以用volatile关键字。它的作用是保证对象在内存中的可见性。

修改ThreadDemo中的count字段

private volatile int count = 0;

 

此时执行结果:900 或其他,与预期结果不符合。

此时还是并未得出正确执行结果。为什么?听我细细道来。

线程安全主要体现在三个方面:

原子性:提供了互斥访问,同一时刻只能有一个线程对它进行操作
可见性:一个线程对主内存的修改可以及时的被其他线程观察到
有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序
目前可见性已经实现了,缺少原子性的操作,因为同一时刻,多个线程对其操作,会将改动后的最新值读取到自己的工作内存进行操作,最终只能得到后一个执行线程操作的结果,所以相当于少了一步操作,就会造成数据的不一致。

此时可以使用JUC的Atomic包下面的类来进行操作。

 

 

Atomic类是使用CAS+volatile来实现原子性与可见性的。

我们来改造一下TheadDemo.java中的实现方法

 

package com.spring.security.test;
 
import java.util.concurrent.atomic.AtomicInteger;
 
public class ThreadDemo implements Runnable {
    
    private AtomicInteger count = new AtomicInteger(0);
 
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            // 递增
            count.getAndIncrement();
        }
    }
    
    
    public int getCount() {
        return count.get();
    }
}

执行结果: 1000,符合预期值。

 接下来我们来分析一下AtomicInteger类的源码:

private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
 
static {
    try {
        valueOffset = unsafe.objectFieldOffset
            (AtomicInteger.class.getDeclaredField("value"));
    } catch (Exception ex) { throw new Error(ex); }
}
 
private volatile int value;

Unsafe类是不安全的类,它提供了一些底层的方法,我们是不能使用这个类的。AtomicInteger的值保存在value中,而valueOffset是value在内存中的偏移量,利用静态代码块使其类一加载的时候就赋值。value值使用volatile,保证其可见性。

    /**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
 
    return var5;
}

var1表示当前对象,var2表示value在内存中的偏移量,var4为增加的值。var5为调用底层方法获取value的值

compareAndSwapInt方法通过var1和var2获取当前内存中的value值,并与var5进行比对,如果一致,就将var5+var4的值赋给value,并返回true,否则返回false

由do while语句可知,如果这次没有设置进去值,就重复执行此过程。这一过程称为自旋。

compareAndSwapInt是JNI(Java Native Interface)提供的方法,可以是其他语言写的。

 

 

 

 

 

总结,本质,我先获取内存中的最新值作为期望值  var5 = this.getIntVolatile(var1, var2) ,

然后相加的这一时刻的时候也会获取这个时刻内存中最新的值 compareAndSwapInt,如果一样,

则进行相加。自己总结,

                                               -------------------------2021-05-31

 

 

三、与synchronized比较
使用synchronized进行加法:

package com.spring.security.test;
 
 
public class ThreadDemo implements Runnable {
    
    private int count = 0;
 
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            // 递增
            synchronized (ThreadDemo.class) {
                count++;
            }
        }
    }
    
    
    public int getCount() {
        return count;
    }
}

运行结果: 1000,符合预期值。

 

使用synchronized和AtomicInteger都能得到预期结果,但是他们之间各有什么劣势呢?

synchronized是重量级锁,是悲观锁,就是无论你线程之间发不发生竞争关系,它都认为会发生竞争,从而每次执行都会加锁。

在并发量大的情况下,如果锁的时间较长,那将会严重影响系统性能。

CAS操作中我们可以看到getAndAddInt方法的自旋操作,如果长时间自旋,那么肯定会对系统造成压力。而且如果value值从A->B->A,那么CAS就会认为这个值没有被操作过,这个称为CAS操作的"ABA"问题。

 

 


————————————————
版权声明:本文为CSDN博主「与李」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_37171817/article/details/106122274

 

 

------------------------------------------------------------------------------------

Java中的AQS

什么是AQS 

AQS即AbstractQueuedSynchronizer(抽象队列同步器),一个并发包的基础组件,用来实现各种锁,各种同步组件的。它包含了state变量、加锁线程、等待队列等并发中的核心组件。我们常用的比如ReentrantLock,CountDownLatch等等基础类库都是基于AQS实现的。

 

AQS的原理和结构

AQS核心思想是,如果被请求的共享资源空闲,那么就将当前请求资源的线程设置为有效的工作线程,将共享资源设置为锁定状态;如果共享资源被占用,就通过一个基于一个双向链表的队列阻塞等待。

AQS的同步状态——State。AQS中维护了一个名为state的字段,意为同步状态,是由Volatile修饰的,用于展示当前临界资源的获锁情况。

下面提供了几个访问这个字段的方法:

 

 

 

 

其实tryRelease就是会调用setState方法将state的数量减一,如果state值为0,则彻底释放锁,会将“加锁线程”变量也设置为null,接下来,会从等待队列的队头唤醒线程2重新尝试加锁。其实逻辑说起来就这么多,大家还可以多看源码,看看是怎么实现的。

 

参考

https://mp.weixin.qq.com/s/sA01gxC4EbgypCsQt5pVog

https://mp.weixin.qq.com/s/zdn54VeNSsabwDd3CBvSoA

 

关注公众号:蜜蜂技术巢了解更多知识
————————————————
版权声明:本文为CSDN博主「久梦歌行」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/z83986976/article/details/106987210

 

https://www.cnblogs.com/waterystone/p/4920797.html                     AQS

 

posted @ 2021-05-28 13:57  abcdefghijklmnop  阅读(326)  评论(0编辑  收藏  举报