原子性操作
原子即為不可再分的,原子操作即要么所有操作全部完成 要么全不完成飘庄。
用synchronized包圍的代碼塊或方法就是原子操作脑蠕。對于線程來講,synchronized包圍代碼,只會全部完成谴仙,不會執(zhí)行一半而中斷迂求。
synchronized是一個很重的操作,如果執(zhí)行代碼很簡單晃跺,例如i++,很多線程都阻塞在外面很劃不來揩局,為了解決這種問題,引出了CAS(Compare And Swap)掀虎,比較并且交換凌盯。系統(tǒng)提供了很多原子變量,Atomic開頭的變量都是實現(xiàn)了CAS烹玉,例如AtomicBoolean驰怎、AtomicInteger等。
CAS
我們下面舉個例子:
假如我們現(xiàn)在有多個線程執(zhí)行count++這個操作二打。
- 首先從內(nèi)存中取出count的值县忌。(假如這時是0)
- 然后進行累加操作。(count變?yōu)榱?)
- 這時在從內(nèi)存中取出count的值继效,如果與第一步取到的值相等症杏,則將累加操作后的值寫入內(nèi)存,否則 說明有別的線程改過了瑞信,這時再重復(fù)第一步操作厉颤,直到完成賦值操作。
總結(jié):
獲取內(nèi)存中的值 喧伞,進行操作走芋,再寫入內(nèi)存的時候,進行判斷當(dāng)前內(nèi)存中的值是否與之前取出的值是否一致潘鲫,不一致的話以內(nèi)存中的新值翁逞,重新計算,反復(fù)執(zhí)行(自旋溉仑,其實就是死循環(huán))挖函,直到內(nèi)存中的值沒有在經(jīng)過修改,才進行寫入操作浊竟。
這里就引出了兩個概念:
- 悲觀鎖 先鎖再操作(synchronized)
- 樂觀鎖 先操作再判斷是否進行修改
CAS和synchronized性能比較:
正常生產(chǎn)環(huán)境下CAS的效率是要高于synchronized怨喘,因為synchronized會阻塞線程,線程阻塞的時候會發(fā)生上下文切換(3-5ms)振定,CAS執(zhí)行指令的時間大概在0.6ns必怜。
高度競爭,特意設(shè)計的情況下synchronized會優(yōu)于CAS后频。
為什么有CAS還需要synchronized
- ABA問題:
假設(shè)當(dāng)前有兩個線程ThreadA和ThreadB梳庆,一個變量A
ThreadA想把A修改為B暖途,根據(jù)上面介紹的CAS原理,我們知道膏执,修改的時候會判斷A是否被修改過驻售,用段偽代碼表示也就是if(A==A) A = B
ThreadB比ThreadA跑的快,ThreadA把A修改為C更米,然后又改回為A欺栗。
可是ThreadA認為A沒被修改過。
解決辦法:版本戳 要求每個線程修改值的時候 加入一個版本戳
jdk中提供了相關(guān)的操作
AtomicMarkReference(有沒有變過) AtomicStampedReference(變了幾次)
public class UserAtomicMarkable {
static AtomicStampedReference<String> atomicStampedReference = new AtomicStampedReference<>("red",0);
public static void main(String[] args) throws InterruptedException {
final int oldStamp = atomicStampedReference.getStamp();
final String oldReference = atomicStampedReference.getReference();
System.out.println(oldReference + "----------" + oldStamp);
Thread threadA = new Thread(){
@Override
public void run() {
super.run();
System.out.println(Thread.currentThread().getName() + "當(dāng)前變量值 " + oldReference + "-當(dāng)前版本戳 " + oldStamp );
atomicStampedReference.compareAndSet(oldReference,oldReference + "Java",oldStamp,oldStamp + 1);
}
};
Thread threadB = new Thread(){
@Override
public void run() {
super.run();
String reference = atomicStampedReference.getReference();
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "當(dāng)前變量值 " + reference + "-當(dāng)前版本戳 " + stamp );
atomicStampedReference.compareAndSet(reference,reference + "C",stamp,stamp + 1);
String reference1 = atomicStampedReference.getReference();
int stamp1 = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "當(dāng)前變量值 " + reference1 + "-當(dāng)前版本戳 " + stamp1 );
}
};
threadA.start();
threadA.join();
threadB.start();
threadB.join();
System.out.println(atomicStampedReference.getReference() + "----------" + atomicStampedReference.getStamp());
}
}
開銷問題
當(dāng)競爭激烈的時候征峦,會存在長時間完成不了操作 迟几,造成自旋,一直重試眶痰,會占用CPU資源瘤旨。
解決辦法:換成synchronized梯啤。只能保證一個共享變量的原子操作
CAS操作時竖伯,只能針對某個內(nèi)存地址上的值進行修改,而一個地址往往只能保存一個變量因宇。
解決辦法:AtomicReference把多個變量打包到一個對象中 替換對象
public class UseAtomicReference {
static AtomicReference<User> atomicReference;
public static void main(String[] args) {
User user = new User("xiaoming",22);
atomicReference = new AtomicReference<>(user);
User updateUser = new User("xiaohong",16);
atomicReference.compareAndSet(user,updateUser);
System.out.println(atomicReference.get());
System.out.println(user);
}
static class User{
private String name;
private int age;
public User(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
}