3.1 原子操作CAS

一、原子操作

1、概念

原子操作就是在執行某一操作時,不會被打斷。不會存在中間態,所以就不會存在併發引起的數據不一致問題。

對於arm來說,單條彙編指令都是原子的,多核smp也是,因爲有總線仲裁所以cpu可以單獨佔用總線直到指令結束,多核系統中的原子操作通常使用內存柵障(memory barrier)來實現,即一個CPU核在執行原子操作時,其他CPU核必須停止對內存操作或者不對指定的內存進行操作,這樣才能避免數據競爭問題。

對於非原子操作,我們可以使用syn(加鎖)操作來實現。但是會存在以下問題
syn是基於阻塞的鎖的機制
1、被阻塞的線程優先級很高,但是由於未獲取鎖而一直阻塞。
2、持有鎖的線程不釋放鎖
3、大量的競爭、消耗cpu、以及可能會帶來思死鎖問題

二、CAS

1、下面是我之前整理學習過的有關於CAS的知識,包括:概念、存在的問題以及解決方案等等。

CAS操作的概念,存在的問題和解決方案

2、JDK中相關原子操作類的使用

  • 更新基本類型
    AtomicBoolean、AtomicInteger、AtomicIong
    下面展示一些 內聯代碼片
package cn.enjoy.controller.thread.CAS;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author:wangle
 * @description:使用CASInteger
 * @version:V1.0
 * @date:2020-03-27 22:11
 **/
public class UseAtoicInt {
    static AtomicInteger atomicInteger = new AtomicInteger(10);

	public static void main(String[] args){
        System.out.println(atomicInteger.getAndIncrement());
        System.out.println(atomicInteger.incrementAndGet());
        System.out.println(atomicInteger.get() );
    }
}

在這裏插入圖片描述
getAndIncrement是先獲取值,再進行加一、incrementAndGet則是相反的,所以看到的結果是10、12、12

  • 更新引用類型
    AtomicReference、AtomicStampedReference、AtomicMarkableReference(後兩個是解決ABA問題的關鍵)
package cn.enjoy.controller.thread.CAS;

import java.util.concurrent.atomic.AtomicReference;
/**
 * @author:wangle
 * @description:使用CAS引用類型
 * @version:V1.0
 * @date:2020-03-27 22:27
 **/
public class UseReference {
    static class User{
        private String name;
        private int age;
        public User(String name,int age){
            this.age=age;
            this.name=name;
        }
        public String getName(){
            return this.name;
        }
        public int getAge(){
            return age;
        }
    }
    static AtomicReference<User> reference = new AtomicReference<User>();

    public static void main(String[] args){
        User user = new User("wangle",23);
        reference.set(user);
        User updateUser = new User("zhangsan",23);
        reference.compareAndSet(user,updateUser);
        System.out.println(reference.get().getName());
        System.out.println(reference.get().getAge());
        System.out.println(user.getName());
        System.out.println(user.getAge());
    }
}

在這裏插入圖片描述
可以看到結果,我們在將引用設置到原子操作類之後,兩個對象可以說是已經是不同的對象,引用已經發生改變,接下來操作的是原子操作當中的元素,user本身的值是未改變的。

AtomicStampedReference、AtomicMarkableReference是帶有版本號的CAS操作,能很好的解決ABA問題,關於ABA問題詳看CAS操作的概念,存在的問題和解決方案

AtomicMarkableReference:關心的是有沒有人改變過,版本號是boolean AtomicStampedReference:關心的是被改變過多少次,版本號是int

package cn.enjoy.controller.thread.CAS;

import java.util.concurrent.atomic.AtomicStampedReference;

/**
 * @author:wangle
 * @description:帶有版本號的CAS操作
 * @version:V1.0
 * @date:2020-03-27 22:42
 **/
public class UseStampedReference {
    static AtomicStampedReference<String> atomicStampedReference = new AtomicStampedReference<>("JAVA",0);
    
    public static void main(String[] args)throws InterruptedException{
        //初始的版本號
        final int oldStamped = atomicStampedReference.getStamp();
        //初始的name
        final String oldName =  atomicStampedReference.getReference();
        System.out.println("name:"+oldName+"========="+"版本:"+oldStamped);

        Thread change = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"當前的name:"+oldName+"當前的版本號:"+oldStamped+
                        atomicStampedReference.compareAndSet(oldName,"C++",oldStamped,oldStamped+1));
            }
        });
        Thread errorChange = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"當前的name:"+atomicStampedReference.getReference()+"當前的版本號:"+atomicStampedReference.getStamp()+
                        atomicStampedReference.compareAndSet(atomicStampedReference.getReference(),"golang",oldStamped,oldStamped+1));
            }
        });
        change.start();
        change.join();
        errorChange.start();
        errorChange.join();
        System.out.println("當前的name:"+atomicStampedReference.getReference()+"當前的版本號:"+atomicStampedReference.getStamp());
    }
}

在這裏插入圖片描述
可以看到,當版本號和期望值都是正確的時候我們將java->C++,並且將版本號自增了1,當版本號不對時儘管期望值是對的,將C+±>Golang的時候返回的是false,表示版本號衝突,未更新成功,我們在最後打印出來的結果也證實了這一點。

  • 更新數組
    AtomicInteger、AtomicIong、AtomicReference
package cn.enjoy.controller.thread.CAS;

import java.util.concurrent.atomic.AtomicIntegerArray;

/**
 * @author:wangle
 * @description:使用CASArray
 * @version:V1.0
 * @date:2020-03-27 23:07
 **/
public class UseArray {
    static int[] arr = new int[] {1,2};
    static AtomicIntegerArray array = new AtomicIntegerArray(arr);
    
    public static void main(String[] args){
        array.getAndSet(0,3);
        System.out.println(array.get(0));
        System.out.println(arr[0]);
    }
}

在這裏插入圖片描述
可以看到我們操作的是具體的index下標的值
和引用原子操作一樣,原數組中的值未改變,改變的只是原子操作類中的數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章