天天看點

CAS+自旋鎖

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方法詳解

  源碼

CAS+自旋鎖

  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指派的值
    }

      
CAS+自旋鎖

     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();

    }

}