1.CAS是什麼(CompareAndSet)
CAS(Compare and swap)比較和替換是設計并發算法時用到的一種技術。簡單來說,比較和替換是使用一個期望值和一個變量的目前值進行比較,如果目前變量的值與我們期望的值相等,就使用一個新值替換目前變量的值。
2.CAS的使用場景
我們先看看下面的例子
package com.atguigu.springcloud.test;
import org.omg.CORBA.Current;
/**
* @Classname Demo
* @Description TODO
* @Date 2021/4/25 0025 下午 3:25
* @Created by jcc
*/
public class Demo {
int i = 0;
public static void main(String[] args) {
Demo d = new Demo();
new Thread(()->{
while (d.i < 100){
int j = d.i;
System.out.println(Thread.currentThread().getName() + "-j-" + j + "-i-" + ++d.i);
}
},"aa").start();;
new Thread(()->{
while (d.i < 100){
int j = d.i;
System.out.println(Thread.currentThread().getName() + "-j-" + j + "-i-" + ++d.i);
}
},"bb").start();
}
}
//一次的輸出結果
bb-j-0-i-1
bb-j-2-i-3
aa-j-0-i-2
aa-j-4-i-5
aa-j-5-i-6
aa-j-6-i-7
aa-j-7-i-8
........
看這個的輸出結果的第三行 aa-j-0-i-2 輸出的j的值是0,i的值是2
說明aa線程最開始擷取到的i的值是0,而在++i的操作時,i已經被bb線程變為1了,是以++i的輸出結果是2
如果我們想要aa線程和bb線程的j的值和++i的值必須是相差為1,也就是說,一個線程在對i進行操作的過程中不能被另外一個線程幹擾
方法1,加鎖
package com.atguigu.springcloud.test;
import org.omg.CORBA.Current;
/**
* @Classname Demo
* @Description TODO
* @Date 2021/4/25 0025 下午 3:25
* @Created by jcc
*/
public class Demo {
int i = 0;
public static void main(String[] args) {
Demo d = new Demo();
new Thread(()->{
synchronized (Demo.class){
while (d.i < 100){
int j = d.i;
System.out.println(Thread.currentThread().getName() + "-j-" + j + "-i-" + ++d.i);
}
}
},"aa").start();;
new Thread(()->{
synchronized (Demo.class){
while (d.i < 100){
int j = d.i;
System.out.println(Thread.currentThread().getName() + "-j-" + j + "-i-" + ++d.i);
}
}
},"bb").start();
}
}
方法2,使用原子類
package com.atguigu.springcloud.test;
import org.omg.CORBA.Current;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Classname Demo
* @Description TODO
* @Date 2021/4/25 0025 下午 3:25
* @Created by jcc
*/
public class Demo1 {
AtomicInteger in = new AtomicInteger(); //預設值是0
public static void main(String[] args) {
Demo1 d = new Demo1();
new Thread(()->{
while (d.in.get() < 100){
int j = d.in.get();
int next = j + 1;
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (!d.in.compareAndSet(j,next)){
System.out.println(Thread.currentThread().getName() + "-false -j-" + j + "-i-" + d.in.get());
j = d.in.get();
next = j + 1;
}
System.out.println(Thread.currentThread().getName() + "true -j-" + j + "-i-" + d.in.get());
}
},"aa").start();;
new Thread(()->{
while (d.in.get() < 100){
int j = d.in.get();
int next = j + 1;
try {
TimeUnit.MILLISECONDS.sleep(110);
} catch (InterruptedException e) {
e.printStackTrace();
}
while (!d.in.compareAndSet(j,next)){
System.out.println(Thread.currentThread().getName() + "-false -j-" + j + "-i-" + d.in.get());
j = d.in.get();
next = j + 1;
}
System.out.println(Thread.currentThread().getName() + "-true -j-" + j + "-i-" + d.in.get());
}
},"bb").start();
}
}
這裡面最關鍵的代碼
while (!d.in.compareAndSet(j,next)){
System.out.println("false -j-" + j + "-i-" + d.in.get());
j = d.in.get();
next = j + 1;
}
compareAndSet這個方法的意思是 把j的值和主線程中in的值進行對比,如果一緻,則in的值變為next傳回true,否則不進行的操作,傳回false,這裡就是用到了CAS。而外面加上while,則是采用了自旋鎖的思想。當j的值和in在主記憶體中的值不一緻時,重新把j的值指派為主記憶體中in的值,再調用compareAndSet方法,知道j和in的值一緻
看下面部分輸出結果
aatrue -j-0-i-1
bb-false -j-0-i-1
bb-true -j-1-i-2
aa-false -j-1-i-2
aatrue -j-2-i-3
......
看着可輸出結果
aatrue -j-0-i-1 aa線程擷取in的值是0指派給j,compareAndSet比較j和in的值一樣,可以,把next的值指派給in
bb-false -j-0-i-1 aa線程擷取in的值是0指派給j,compareAndSet比較j和in的值不一樣,此時in已經變為1了,是以不把next的值指派給in。
bb-true -j-1-i-2 把in的值1重新指派給j,i+1指派給next,再去調用compareAndSet,此時j=1,in的值也為1,可以,把next的值指派給in
3.關于compareAndSet方法詳解
源碼

AtomicInteger 的變量value被volatile修飾,所有線程可見
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
//參數1 AtomicInteger對象 valueOffset AtomicInteger對象的記憶體位址 expect期望值(和記憶體中的值作比較) update:給AtomicInteger指派的值
}
compareAndSet調用的是unsafe類的compareAndSwapInt,而compareAndSwapInt方法是底層的方法,就是用來比較記憶體中的值和期望的值是否一緻,如果一緻,把update的值指派給它,傳回true,否則不指派。傳回false
5.自旋鎖
嘗試擷取鎖的線程不會阻塞,而是采用循環的方式去嘗試擷取鎖, 好處是減少上下文的切換時間,壞處是循環會占用CPU
package com.atguigu.springcloud.test;
import org.omg.CORBA.Current;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
/**
* @Classname ZiXuanSuoDemo
* @Description TODO
* @Date 2021/4/25 0025 下午 3:02
* @Created by jcc
*/
public class ZiXuanSuoDemo {
//自旋鎖:嘗試擷取鎖的線程不會阻塞,而是采用循環的方式去嘗試擷取鎖,
// 好處是減少上下文的切換時間,壞處是循環會占用CPU
//1.原子類-Thread
AtomicReference<Thread> atomicReference = new AtomicReference<>();
void myLock(){
Thread thread = Thread.currentThread();
while (!atomicReference.compareAndSet(null,thread)){
}
System.out.println(thread.getName() + "成功擷取鎖");
}
void unLock(){
Thread thread = Thread.currentThread();
atomicReference.set(null);
System.out.println(thread.getName() + "成功釋放鎖");
}
public static void main(String[] args) {
ZiXuanSuoDemo de = new ZiXuanSuoDemo();
new Thread(()->{
System.out.println("AA進來了");
de.myLock();//去擷取鎖
//成功擷取鎖後,執行下面的操作
try {
System.out.println("Aa執行操作");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
de.unLock();
},"Aa").start();
new Thread(()->{
System.out.println("Bb進來了");
de.myLock(); //去擷取鎖
//成功擷取鎖後,執行下面的操作
try {
System.out.println("Bb執行操作");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
de.unLock();
},"Bb").start();
}
}