一、原子操作
1、概念
原子操作就是在執行某一操作時,不會被打斷。不會存在中間態,所以就不會存在併發引起的數據不一致問題。
對於arm來說,單條彙編指令都是原子的,多核smp也是,因爲有總線仲裁所以cpu可以單獨佔用總線直到指令結束,多核系統中的原子操作通常使用內存柵障(memory barrier)來實現,即一個CPU核在執行原子操作時,其他CPU核必須停止對內存操作或者不對指定的內存進行操作,這樣才能避免數據競爭問題。
對於非原子操作,我們可以使用syn(加鎖)操作來實現。但是會存在以下問題
syn是基於阻塞的鎖的機制
1、被阻塞的線程優先級很高,但是由於未獲取鎖而一直阻塞。
2、持有鎖的線程不釋放鎖
3、大量的競爭、消耗cpu、以及可能會帶來思死鎖問題
二、CAS
1、下面是我之前整理學習過的有關於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下標的值
和引用原子操作一樣,原數組中的值未改變,改變的只是原子操作類中的數據。