原文鏈接:Java并發(fā)編程-無鎖CAS與Unsafe類及其并發(fā)包Atomic - CSDN博客
通過前面的分析我們已基本理解了無鎖CAS的原理并對Java中的指針類Unsafe類有了比較全面的認(rèn)識,下面進(jìn)一步分析CAS在Java中的應(yīng)用审孽,即并發(fā)包中的原子操作類(Atomic系列),從JDK 1.5開始提供了java.util.concurrent.atomic包亡脑,在該包中提供了許多基于CAS實現(xiàn)的原子操作類,用法方便潭兽,性能高效,主要分以下4種類型。
原子更新基本類型
原子更新基本類型主要包括3個類:
????AtomicBoolean:原子更新布爾類型
????AtomicInteger:原子更新整型
????AtomicLong:原子更新長整型
這3個類的實現(xiàn)原理和使用方式幾乎是一樣的姨伟,這里我們以AtomicInteger為例進(jìn)行分析,AtomicInteger主要是針對int類型的數(shù)據(jù)執(zhí)行原子操作豆励,它提供了原子自增方法夺荒、原子自減方法以及原子賦值方法等,鑒于AtomicInteger的源碼不多良蒸,我們直接看源碼:
public class AtomicInteger extends Number implements java.io.Serializable{
????private static final long serialVersionUID = 6214790243416807050L;
? ? // 獲取指針類Unsafe
? ? private static final Unsafe unsafe = Unsafe.getUnsafe();
? ? //下述變量value在AtomicInteger實例對象內(nèi)的內(nèi)存偏移量
? ? private static final long valueOffset;
? ? static {
? ? ? ? try {
? ? ? ? ? //通過unsafe類的objectFieldOffset()方法技扼,獲取value變量在對象內(nèi)存中的偏移
? ? ? ? ? //通過該偏移量valueOffset,unsafe類的內(nèi)部方法可以獲取到變量value對其進(jìn)行取值或賦值操作
? ? ? ? ? ? valueOffset = unsafe.objectFieldOffset(AtomicInteger.class.getDeclaredField("value"));
? ? ? ? } catch (Exception ex) {
?????????????throw new Error(ex);?
? ? ? ?}
? ? }
? //當(dāng)前AtomicInteger封裝的int變量value
? ? private volatile int value;
? ? public AtomicInteger(int initialValue) {
? ? ? ? value = initialValue;
? ? }
? ? public AtomicInteger() {
? ? }
? //獲取當(dāng)前最新值嫩痰,
? ? public final int get() {
? ? ? ? return value;
? ? }
? ? //設(shè)置當(dāng)前值剿吻,具備volatile效果,方法用final修飾是為了更進(jìn)一步的保證線程安全始赎。
? ? public final void set(int newValue) {
? ? ? ? value = newValue;
? ? }
? ? //最終會設(shè)置成newValue和橙,使用該方法后可能導(dǎo)致其他線程在之后的一小段時間內(nèi)可以獲取到舊值,有點類似于延遲加載
? ? public final void lazySet(int newValue) {
? ? ? ? unsafe.putOrderedInt(this, valueOffset, newValue);
? ? }
? //設(shè)置新值并獲取舊值造垛,底層調(diào)用的是CAS操作即unsafe.compareAndSwapInt()方法
? ? public final int getAndSet(int newValue) {
? ? ? ? return unsafe.getAndSetInt(this, valueOffset, newValue);
? ? }
? //如果當(dāng)前值為expect魔招,則設(shè)置為update(當(dāng)前值指的是value變量)
? ? public final boolean compareAndSet(int expect, int update) {
? ? ? ? return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
? ? }
? ? //當(dāng)前值加1返回舊值,底層CAS操作
? ? public final int getAndIncrement() {
? ? ? ? return unsafe.getAndAddInt(this, valueOffset, 1);
? ? }
? ? //當(dāng)前值減1五辽,返回舊值办斑,底層CAS操作
? ? public final int getAndDecrement() {
? ? ? ? return unsafe.getAndAddInt(this, valueOffset, -1);
? ? }
? //當(dāng)前值增加delta,返回舊值杆逗,底層CAS操作
? ? public final int getAndAdd(int delta) {
? ? ? ? return unsafe.getAndAddInt(this, valueOffset, delta);
? ? }
? ? //當(dāng)前值加1乡翅,返回新值,底層CAS操作
? ? public final int incrementAndGet() {
? ? ? ? return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
? ? }
? ? //當(dāng)前值減1罪郊,返回新值蠕蚜,底層CAS操作
? ? public final int decrementAndGet() {
? ? ? ? return unsafe.getAndAddInt(this, valueOffset, -1) - 1;
? ? }
? //當(dāng)前值增加delta,返回新值悔橄,底層CAS操作
? ? public final int addAndGet(int delta) {
? ? ? ? return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
? ? }
? //省略一些不常用的方法....
}
通過上述的分析靶累,可以發(fā)現(xiàn)AtomicInteger原子類的內(nèi)部幾乎是基于前面分析過Unsafe類中的CAS相關(guān)操作的方法實現(xiàn)的,這也同時證明AtomicInteger是基于無鎖實現(xiàn)的癣疟,這里重點分析自增操作實現(xiàn)過程挣柬,其他方法自增實現(xiàn)原理一樣。
//當(dāng)前值加1睛挚,返回新值邪蛔,底層CAS操作
public final int incrementAndGet() {
? ? return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
我們發(fā)現(xiàn)AtomicInteger類中所有自增或自減的方法都間接調(diào)用Unsafe類中的getAndAddInt()方法實現(xiàn)了CAS操作,從而保證了線程安全扎狱,關(guān)于getAndAddInt其實前面已分析過侧到,它是Unsafe類中1.8新增的方法勃教,源碼如下:
//Unsafe類中的getAndAddInt方法
public final int getAndAddInt(Object o, long offset, int delta) {
? ? ? ? int v;
? ? ? ? do {
? ? ? ? ? ? v = getIntVolatile(o, offset);
? ? ? ? } while (!compareAndSwapInt(o, offset, v, v + delta));
? ? ? ? return v;
? ? }
可看出getAndAddInt通過一個while循環(huán)不斷的重試更新要設(shè)置的值,直到成功為止床牧,調(diào)用的是Unsafe類中的compareAndSwapInt方法荣回,是一個CAS操作方法。這里需要注意的是戈咳,上述源碼分析是基于JDK1.8的,如果是1.8之前的方法壕吹,AtomicInteger源碼實現(xiàn)有所不同著蛙,是基于for死循環(huán)的,如下:
//JDK 1.7的源碼耳贬,由for的死循環(huán)實現(xiàn)踏堡,并且直接在AtomicInteger實現(xiàn)該方法,
//JDK1.8后咒劲,該方法實現(xiàn)已移動到Unsafe類中顷蟆,直接調(diào)用getAndAddInt方法即可
public final int incrementAndGet() {
? ? for (;;) {
? ? ? ? int current = get();
? ? ? ? int next = current + 1;
? ? ? ? if (compareAndSet(current, next))
? ? ? ? ? ? return next;
? ? }
}
ok~,下面簡單看個Demo,感受一下AtomicInteger使用方式:
public class AtomicIntegerDemo {
?????//創(chuàng)建AtomicInteger,用于自增操作
????static AtomicInteger i = new AtomicInteger();
? ? public static class AddThread implements Runnable {
? ? ? ? public void run(){
? ? ? ? ? for(int k=0; k<10000; k++)
? ? ? ? ? ? ? i.incrementAndGet();
? ? ? ? }
? ? }
? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? Thread[] ts = new Thread[10];
? ? ? ? //開啟10條線程同時執(zhí)行i的自增操作
? ? ? ? for(int k=0;k<10;k++){
? ? ? ? ? ? ts[k]=new Thread(new AddThread());
? ? ? ? }
? ? ? ? //啟動線程
? ? ? ? for(int k=0;k<10;k++) {
????????????ts[k].start();
????????}
? ? ? ? for(int k=0;k<10;k++) {
????????????ts[k].join();
????????}
? ? ? ? System.out.println(i);
????????//輸出結(jié)果:100000
? ? }
}
在Demo中腐魂,使用原子類型AtomicInteger替換普通int類型執(zhí)行自增的原子操作帐偎,保證了線程安全。至于AtomicBoolean和AtomicLong的使用方式以及實現(xiàn)原理是一樣蛔屹,大家可以自行查閱源碼削樊。
原子更新引用
原子更新引用類型可以同時更新引用類型,這里主要分析一下AtomicReference原子類兔毒,即原子更新引用類型漫贞。先看看其使用方式,如下:
public class AtomicReferenceDemo2 {
? ? public static AtomicReference atomicUserRef = new AtomicReference();
? ? public static void main(String[] args) {
? ? ? ? User user = new User("zejian", 18);
? ? ? ? atomicUserRef.set(user);
? ? ? ? User updateUser = new User("Shine", 25);
? ? ? ? atomicUserRef.compareAndSet(user, updateUser);
? ? ? ? //執(zhí)行結(jié)果:User{name='Shine', age=25}? ? ? ? ? ? ? ????????
????????System.out.println(atomicUserRef.get().toString());?
? ? }
? ? static class User {
? ? ? ? public String name;
? ? ? ? private int age;
? ? ? ? public User(String name, int age) {
? ? ? ? ? ? this.name = name;
? ? ? ? ? ? this.age = age;
? ? ? ? }
? ? ? ? public String getName() {
? ? ? ? ? ? return name;
? ? ? ? }
? ? ? ? @Override
? ? ? ? public String toString() {
? ? ? ? ? ? return "User{" +
? ? ? ? ? ? ? ? ? ? "name='" + name + '\'' +
? ? ? ? ? ? ? ? ? ? ", age=" + age +
? ? ? ? ? ? ? ? ? ? '}';
? ? ? ? }
? ? }
}
那么AtomicReference原子類內(nèi)部是如何實現(xiàn)CAS操作的呢育叁?
public class AtomicReference implements java.io.Serializable {
? ? private static final Unsafe unsafe = Unsafe.getUnsafe();
? ? private static final long valueOffset;
? ? static {
? ? ? ? try {
? ? ? ? ? ? valueOffset = unsafe.objectFieldOffset(AtomicReference.class.getDeclaredField("value"));
? ? ? ? } catch (Exception ex) {
?????????????throw new Error(ex);
? ? ? ? }
? ? }
? ? //內(nèi)部變量value迅脐,Unsafe類通過valueOffset內(nèi)存偏移量即可獲取該變量
? ? private volatile V value;
????//CAS方法,間接調(diào)用unsafe.compareAndSwapObject(),它是一個
????//實現(xiàn)了CAS操作的native方法
????public final boolean compareAndSet(V expect, V update) {
? ? ? ? return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
????}
????//設(shè)置并獲取舊值
????public final V getAndSet(V newValue) {
? ? ? ? return (V)unsafe.getAndSetObject(this, valueOffset, newValue);
? ?}
? ? //省略其他代碼......
}
????//Unsafe類中的getAndSetObject方法豪嗽,實際調(diào)用還是CAS操作
????public final Object getAndSetObject(Object o, long offset, Object newValue) {
? ? ????Object v;
? ? ?????do {
? ? ? ? ?????v = getObjectVolatile(o, offset);
? ? ?????} while (!compareAndSwapObject(o, offset, v, newValue));
? ? ? ? ?return v;
? }
從源碼看來谴蔑,AtomicReference與AtomicInteger的實現(xiàn)原理基本是一樣的,最終執(zhí)行的還是Unsafe類昵骤,關(guān)于AtomicReference的其他方法也是一樣的树碱,如下:
紅框內(nèi)的方法是Java8新增的,可以基于Lambda表達(dá)式對傳遞進(jìn)來的期望值或要更新的值進(jìn)行其他操作后再進(jìn)行CAS操作变秦,說白了就是對期望值或要更新的值進(jìn)行額外修改后再執(zhí)行CAS更新成榜,在所有的Atomic原子類中幾乎都存在這幾個方法。
原子更新數(shù)組
原子更新數(shù)組指的是通過原子的方式更新數(shù)組里的某個元素蹦玫,主要有以下3個類
????AtomicIntegerArray:原子更新整數(shù)數(shù)組里的元素
????AtomicLongArray:原子更新長整數(shù)數(shù)組里的元素
????AtomicReferenceArray:原子更新引用類型數(shù)組里的元素
這里以AtomicIntegerArray為例進(jìn)行分析赎婚,其余兩個使用方式和實現(xiàn)原理基本一樣刘绣,簡單案例如下图呢,
public class AtomicIntegerArrayDemo {
? ? ? ? static AtomicIntegerArray arr = new AtomicIntegerArray(10);
? ? ????public static class AddThread implements Runnable {
? ? ? ? ????public void run(){
? ? ? ? ? ? ? ????for(int k=0;k<10000;k++)
? ? ? ? ? ? ? ???? //執(zhí)行數(shù)組中元素自增操作,參數(shù)為index,即數(shù)組下標(biāo)
? ? ? ? ? ? ? ? ? arr.getAndIncrement(k%arr.length());
? ? ? ? ????}
? ? ????}
? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? Thread[] ts=new Thread[10];
? ? ? ? //創(chuàng)建10條線程
? ? ? ? for(int k=0;k<10;k++){
? ? ? ? ? ? ts[k]=new Thread(new AddThread());
? ? ? ? }
? ? ? ? //啟動10條線程
? ? ? ? for(int k=0;k<10;k++){
????????????ts[k].start();
????????}
? ? ? ? for(int k=0;k<10;k++){
????????????ts[k].join();
????????}
? ? ? ? //執(zhí)行結(jié)果
? ? ? ? //[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]? ? ? ? ????????
????????System.out.println(arr);
? ? }
}
啟動10條線程對數(shù)組中的元素進(jìn)行自增操作摸吠,執(zhí)行結(jié)果符合預(yù)期。使用方式比較簡單笤昨,接著看看AtomicIntegerArray內(nèi)部是如何實現(xiàn)撩嚼,先看看部分源碼:
public class AtomicIntegerArray implements java.io.Serializable{
?????//獲取unsafe類的實例對象
?????private static final Unsafe unsafe = Unsafe.getUnsafe();
? ? //獲取數(shù)組的第一個元素內(nèi)存起始地址
? ? private static final int base = unsafe.arrayBaseOffset(int[].class);
? ? private static final int shift;
? ? //內(nèi)部數(shù)組
????private final int[] array;
? ? static {
? ? ? ? //獲取數(shù)組中一個元素占據(jù)的內(nèi)存空間
? ? ? ? int scale = unsafe.arrayIndexScale(int[].class);
? ? ? ? //判斷是否為2的次冪停士,一般為2的次冪否則拋異常
? ? ? ? if ((scale & (scale - 1)) != 0)
? ? ? ? ? ? throw new Error("data type scale not a power of two");
? ? ? ? //
? ? ? ? shift = 31 - Integer.numberOfLeadingZeros(scale);
? ? }
? ? private long checkedByteOffset(int i) {
? ? ? ? if (i < 0 || i >= array.length)
? ? ? ? ? ? throw new IndexOutOfBoundsException("index " + i);
? ? ? ? return byteOffset(i);
? ? }
? ? //計算數(shù)組中每個元素的的內(nèi)存地址
? ? private static long byteOffset(int i) {
? ? ? ? return ((long) i << shift) + base;
? ? }
? ? //省略其他代碼......
}
通過前面對Unsafe類的分析,我們知道arrayBaseOffset方法可以獲取數(shù)組的第一個元素起始地址完丽,而arrayIndexScale方法可以獲取每個數(shù)組元素占用的內(nèi)存空間恋技,由于這里是Int類型,而Java中一個int類型占用4個字節(jié)逻族,也就是scale的值為4蜻底,那么如何根據(jù)數(shù)組下標(biāo)值計算每個元素的內(nèi)存地址呢?顯然應(yīng)該是
每個數(shù)組元素的內(nèi)存地址=起始地址+元素下標(biāo) * 每個元素所占用的內(nèi)存空間
與該方法原理相同
//計算數(shù)組中每個元素的的內(nèi)存地址
private static long byteOffset(int i) {
? ? return ((long) i << shift) + base;
}
這是為什么聘鳞,首先來計算出shift的值
shift = 31 - Integer.numberOfLeadingZeros(scale);
其中Integer.numberOfLeadingZeros(scale)是計算出scale的前導(dǎo)零個數(shù)(必須是連續(xù)的)薄辅,scale=4,轉(zhuǎn)成二進(jìn)制為?
00000000 00000000 00000000 00000100?
即前導(dǎo)零數(shù)為29抠璃,也就是shift=2站楚,然后利用shift來定位數(shù)組中的內(nèi)存位置,在數(shù)組不越界時鸡典,計算出前3個數(shù)組元素內(nèi)存地址
//第一個數(shù)組元素源请,index=0 , 其中base為起始地址彻况,4代表int類型占用的字節(jié)數(shù)
address = base + 0 * 4 即address= base + 0 << 2
//第二個數(shù)組元素谁尸,index=1
address = base + 1 * 4 即address= base + 1 << 2
//第三個數(shù)組元素,index=2
address = base + 2 * 4 即address= base + 2 << 2
//........
顯然shift=2纽甘,替換去就是
address= base + i << shift
這就是 byteOffset(int i) 方法的計算原理良蛮。因此byteOffset(int)方法可以根據(jù)數(shù)組下標(biāo)計算出每個元素的內(nèi)存地址。至于其他方法就比較簡單了悍赢,都是間接調(diào)用Unsafe類的CAS原子操作方法决瞳,如下簡單看其中幾個常用方法
//執(zhí)行自增操作,返回舊值左权,i是指數(shù)組元素下標(biāo)
public final int getAndIncrement(int i) {
? ? ? return getAndAdd(i, 1);
}
//指定下標(biāo)元素執(zhí)行自增操作皮胡,并返回新值
public final int incrementAndGet(int i) {
? ? return getAndAdd(i, 1) + 1;
}
//指定下標(biāo)元素執(zhí)行自減操作,并返回新值
public final int decrementAndGet(int i) {
? ? return getAndAdd(i, -1) - 1;
}
//間接調(diào)用unsafe.getAndAddInt()方法
public final int getAndAdd(int i, int delta) {
? ? return unsafe.getAndAddInt(array, checkedByteOffset(i), delta);
}
//Unsafe類中的getAndAddInt方法赏迟,執(zhí)行CAS操作
public final int getAndAddInt(Object o, long offset, int delta) {
? ? ? ? int v;
? ? ? ? do {
? ? ? ? ? ? v = getIntVolatile(o, offset);
? ? ? ? } while (!compareAndSwapInt(o, offset, v, v + delta));
? ? ? ? return v;
? ? }
至于AtomicLongArray和AtomicReferenceArray原子類屡贺,使用方式和實現(xiàn)原理基本一樣。
原子更新屬性
如果我們只需要某個類里的某個字段,也就是說讓普通的變量也享受原子操作甩栈,可以使用原子更新字段類泻仙,如在某些時候由于項目前期考慮不周全,項目需求又發(fā)生變化量没,使得某個類中的變量需要執(zhí)行多線程操作玉转,由于該變量多處使用,改動起來比較麻煩殴蹄,而且原來使用的地方無需使用線程安全究抓,只要求新場景需要使用時,可以借助原子更新器處理這種場景袭灯,Atomic并發(fā)包提供了以下三個類:
????AtomicIntegerFieldUpdater:原子更新整型的字段的更新器漩蟆。
????AtomicLongFieldUpdater:原子更新長整型字段的更新器。
????AtomicReferenceFieldUpdater:原子更新引用類型里的字段妓蛮。
請注意原子更新器的使用存在比較苛刻的條件如下:?
????操作的字段不能是static類型。
????操作的字段不能是final類型的圾叼,因為final根本沒法修改蛤克。
????字段必須是volatile修飾的,也就是數(shù)據(jù)本身是讀一致的夷蚊。
????屬性必須對當(dāng)前的Updater所在的區(qū)域是可見的构挤,如果不是當(dāng)前類內(nèi)部進(jìn)行原子更新器操作不能使用private,protected子類操作父類時修飾符必須是protect權(quán)限及以上惕鼓,如果在同一個package下則必須是default權(quán)限及以上筋现,也就是說無論何時都應(yīng)該保證操作類與被操作類間的可見性。
下面看看AtomicIntegerFieldUpdater和AtomicReferenceFieldUpdater的簡單使用方式
public class AtomicIntegerFieldUpdaterDemo{
?public static class Candidate {
? ? ? ? int id;
? ? ? ? volatile int score;
? ? }
? ? public static class Game {
? ? ? ? int id;
? ? ? ? volatile String name;
? ? ? ? public Game(int id, String name) {
? ? ? ? ? ? this.id = id;
? ? ? ? ? ? this.name = name;
? ? ? ? }
? ? ? ? @Override
? ? ? ? public String toString() {
? ? ? ? ? ? return "Game{" +
? ? ? ? ? ? ? ? ? ? "id=" + id +
? ? ? ? ? ? ? ? ? ? ", name='" + name + '\'' +
? ? ? ? ? ? ? ? ? ? '}';
? ? ? ? }
? ? }
? ? static AtomicIntegerFieldUpdater atIntegerUpdater
? ? ? ? = AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
? ? static AtomicReferenceFieldUpdater atRefUpdate =
? ? ? ? ? ? AtomicReferenceFieldUpdater.newUpdater(Game.class,String.class,"name");
? ? //用于驗證分?jǐn)?shù)是否正確
? ? public static AtomicInteger allScore=new AtomicInteger(0);
? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? final Candidate stu=new Candidate();
? ? ? ? Thread[] t=new Thread[10000];
? ? ? ? //開啟10000個線程
? ? ? ? for(int i = 0 ; i < 10000 ; i++) {
? ? ? ? ? ? t[i]=new Thread() {
? ? ? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? ? ? if(Math.random()>0.4){
? ? ? ? ? ? ? ? ? ? ? ? atIntegerUpdater.incrementAndGet(stu);
? ? ? ? ? ? ? ? ? ? ? ? allScore.incrementAndGet();
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
? ? ? ? ? ? t[i].start();
? ? ? ? }
? ? ? ? for(int i = 0 ; i < 10000 ; i++) {? t[i].join();}
? ? ? ? System.out.println("最終分?jǐn)?shù)score="+stu.score);
? ? ? ? System.out.println("校驗分?jǐn)?shù)allScore="+allScore);
? ? ? ? //AtomicReferenceFieldUpdater 簡單的使用
? ? ? ? Game game = new Game(2,"zh");
? ? ? ? atRefUpdate.compareAndSet(game,game.name,"JAVA-HHH");
? ? ? ? System.out.println(game.toString());
? ? ? ? /**
? ? ? ? * 輸出結(jié)果:
? ? ? ? * 最終分?jǐn)?shù)score=5976
? ? ? ? ? 校驗分?jǐn)?shù)allScore=5976
? ? ? ? ? Game{id=2, name='JAVA-HHH'}
? ? ? ? */? ? }
}
我們使用AtomicIntegerFieldUpdater更新候選人(Candidate)的分?jǐn)?shù)score箱歧,開啟了10000條線程投票矾飞,當(dāng)隨機值大于0.4時算一票,分?jǐn)?shù)自增一次呀邢,其中allScore用于驗證分?jǐn)?shù)是否正確(其實用于驗證AtomicIntegerFieldUpdater更新的字段是否線程安全)洒沦,當(dāng)allScore與score相同時,則說明投票結(jié)果無誤价淌,也代表AtomicIntegerFieldUpdater能正確更新字段score的值申眼,是線程安全的。對于AtomicReferenceFieldUpdater蝉衣,我們在代碼中簡單演示了其使用方式括尸,注意在AtomicReferenceFieldUpdater注明泛型時需要兩個泛型參數(shù),一個是修改的類類型病毡,一個修改字段的類型濒翻。至于AtomicLongFieldUpdater則與AtomicIntegerFieldUpdater類似,不再介紹。接著簡單了解一下AtomicIntegerFieldUpdater的實現(xiàn)原理肴焊,實際就是反射和Unsafe類結(jié)合前联,AtomicIntegerFieldUpdater是個抽象類,實際實現(xiàn)類為AtomicIntegerFieldUpdaterImpl娶眷,
public abstract class AtomicIntegerFieldUpdater {
? ? public static AtomicIntegerFieldUpdater newUpdater(Class tclass,?String fieldName) {
? ? ? ? //實際實現(xiàn)類AtomicIntegerFieldUpdaterImpl
? ? ? ? return new AtomicIntegerFieldUpdaterImpl(tclass, fieldName, Reflection.getCallerClass());
? ? }
}
看看AtomicIntegerFieldUpdaterImpl似嗤,
private static class AtomicIntegerFieldUpdaterImpl?extends AtomicIntegerFieldUpdater {
????????private static final Unsafe unsafe = Unsafe.getUnsafe();
? ? ? ? private final long offset;
????????//內(nèi)存偏移量
? ? ? ? private final Class tclass;
? ? ? ? private final Class cclass;
? ? ? ? AtomicIntegerFieldUpdaterImpl(final Class tclass,?final String fieldName, final Class caller) {
? ? ? ? ? ? final Field field;//要修改的字段
? ? ? ? ? ? final int modifiers;//字段修飾符
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? field = AccessController.doPrivileged(
? ? ? ? ? ? ? ? ? ? new PrivilegedExceptionAction() {
? ? ? ? ? ? ? ? ? ? ? ? public Field run() throws NoSuchFieldException {
? ? ? ? ? ? ? ? ? ? ? ? ? ? return tclass.getDeclaredField(fieldName);//反射獲取字段對象? ? ? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? ? ? });
? ? ? ? ? ? ? ? //獲取字段修飾符
? ? ? ? ? ? ? ? modifiers = field.getModifiers();
? ? ? ? ? ? ????//對字段的訪問權(quán)限進(jìn)行檢查,不在訪問范圍內(nèi)拋異常? ? ? ? ? ? ? ??
????????????????sun.reflect.misc.ReflectUtil.ensureMemberAccess(caller, tclass, null, modifiers);
? ? ? ? ? ? ? ? ClassLoader cl = tclass.getClassLoader();
? ? ? ? ? ? ? ? ClassLoader ccl = caller.getClassLoader();
? ? ? ? ? ? ? ? if ((ccl != null) && (ccl != cl) &&
? ? ? ? ? ? ? ? ? ? ((cl == null) || !isAncestor(cl, ccl))) {
? ? ? ? ? ? ? ????????sun.reflect.misc.ReflectUtil.checkPackageAccess(tclass);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? } catch (PrivilegedActionException pae) {
? ? ? ? ? ? ? ? throw new RuntimeException(pae.getException());
? ? ? ? ? ? } catch (Exception ex) {
? ? ? ? ? ? ? ? throw new RuntimeException(ex);
? ? ? ? ? ? }
? ? ? ? ? ? Class fieldt = field.getType();
? ? ? ? ? ? //判斷是否為int類型
? ? ? ? ? ? if (fieldt != int.class)
? ? ? ? ? ? ? ? throw new IllegalArgumentException("Must be integer type");
? ? ? ? ? ? //判斷是否被volatile修飾
? ? ? ? ? ? if (!Modifier.isVolatile(modifiers))
? ? ? ? ? ? ? ? throw new IllegalArgumentException("Must be volatile type");
? ? ? ? ? ? this.cclass = (Modifier.isProtected(modifiers) && caller != tclass) ? caller : null;
? ? ? ? ? ? this.tclass = tclass;
? ? ? ? ? ? //獲取該字段的在對象內(nèi)存的偏移量,通過內(nèi)存偏移量可以獲取或者修改該字段的值
? ? ? ? ? ? offset = unsafe.objectFieldOffset(field);
? ? ? ? }
? ? }
從AtomicIntegerFieldUpdaterImpl的構(gòu)造器也可以看出更新器為什么會有這么多限制條件了届宠,當(dāng)然最終其CAS操作肯定是通過unsafe完成的烁落,簡單看一個方法,
public int incrementAndGet(T obj) {
? ? ? ? int prev, next;
? ? ? ? do {
? ? ? ? ? ? prev = get(obj);
? ? ? ? ? ? next = prev + 1;
? ? ? ? ? ? //CAS操作
? ? ? ? } while (!compareAndSet(obj, prev, next));
? ? ? ? return next;
}
//最終調(diào)用的還是unsafe.compareAndSwapInt()方法
public boolean compareAndSet(T obj, int expect, int update) {
? ? ? ? ? ? if (obj == null || obj.getClass() != tclass || cclass != null) fullCheck(obj);
? ? ? ? ? ? return unsafe.compareAndSwapInt(obj, offset, expect, update);
}
CAS的ABA問題及其解決方案
假設(shè)這樣一種場景豌注,當(dāng)?shù)谝粋€線程執(zhí)行CAS(V,E,U)操作伤塌,在獲取到當(dāng)前變量V,準(zhǔn)備修改為新值U前轧铁,另外兩個線程已連續(xù)修改了兩次變量V的值每聪,使得該值又恢復(fù)為舊值,這樣的話齿风,我們就無法正確判斷這個變量是否已被修改過药薯,如下圖
這就是典型的CAS的ABA問題,一般情況這種情況發(fā)現(xiàn)的概率比較小救斑,可能發(fā)生了也不會造成什么問題童本,比如說我們對某個做加減法,不關(guān)心數(shù)字的過程脸候,那么發(fā)生ABA問題也沒啥關(guān)系穷娱。但是在某些情況下還是需要防止的,那么該如何解決呢运沦?在Java中解決ABA問題泵额,我們可以使用以下兩個原子類。
AtomicStampedReference
AtomicStampedReference原子類是一個帶有時間戳的對象引用茶袒,在每次修改后梯刚,AtomicStampedReference不僅會設(shè)置新值而且還會記錄更改的時間。當(dāng)AtomicStampedReference設(shè)置對象值時薪寓,對象值以及時間戳都必須滿足期望值才能寫入成功亡资,這也就解決了反復(fù)讀寫時,無法預(yù)知值是否已被修改的窘境向叉,測試demo如下锥腻,
/**
* Created by zejian on 2017/7/2.
* Blog : http://blog.csdn.net/javazejian [原文地址,請尊重原創(chuàng)]
*/public classABADemo{
? ? static AtomicInteger atIn = new AtomicInteger(100);
? ? //初始化時需要傳入一個初始值和初始時間
? ? static AtomicStampedReference atomicStampedR =
? ? ? ? ? ? new AtomicStampedReference(200,0);
? ? static Thread t1 = new Thread(new Runnable() {
? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? //更新為200
? ? ? ? ? ? atIn.compareAndSet(100, 200);
? ? ? ? ? ? //更新為100
? ? ? ? ? ? atIn.compareAndSet(200, 100);
? ? ? ? }
? ? });
? ? static Thread t2 = new Thread(new Runnable() {
? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? TimeUnit.SECONDS.sleep(1);
? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? ? ? boolean flag=atIn.compareAndSet(100,500);
? ? ? ? ? ? System.out.println("flag:"+flag+",newValue:"+atIn);
? ? ? ? }
? ? });
? ? static Thread t3 = new Thread(new Runnable() {
? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? int time=atomicStampedR.getStamp();
? ? ? ? ? ? //更新為200
? ? ? ? ? ? atomicStampedR.compareAndSet(100, 200,time,time+1);
? ? ? ? ? ? //更新為100
? ? ? ? ? ? int time2=atomicStampedR.getStamp();
? ? ? ? ? ? atomicStampedR.compareAndSet(200, 100,time2,time2+1);
? ? ? ? }
? ? });
? ? static Thread t4 = new Thread(new Runnable() {
? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? int time = atomicStampedR.getStamp();
? ? ? ? ? ? System.out.println("sleep 前 t4 time:"+time);
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? TimeUnit.SECONDS.sleep(1);
? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? ? ? boolean flag=atomicStampedR.compareAndSet(100,500,time,time+1);
? ? ? ? ? ? System.out.println("flag:"+flag+",newValue:"+atomicStampedR.getReference());
? ? ? ? }
? ? });
? ? public static? void? main(String[] args) throws InterruptedException {
? ? ? ? t1.start();
? ? ? ? t2.start();
? ? ? ? t1.join();
? ? ? ? t2.join();
? ? ? ? t3.start();
? ? ? ? t4.start();
? ? ? ? /**
? ? ? ? * 輸出結(jié)果:
? ? ? ? flag:true,newValue:500
? ? ? ? sleep 前 t4 time:0
? ? ? ? flag:false,newValue:200
? ? ? ? */
? ? }
}
對比輸出結(jié)果可知,AtomicStampedReference類確實解決了ABA的問題母谎,下面我們簡單看看其內(nèi)部實現(xiàn)原理瘦黑,
public class AtomicStampedReference {
? ? //通過Pair內(nèi)部類存儲數(shù)據(jù)和時間戳
? ? private static class Pair {
? ? ? ? final T reference;
? ? ? ? final int stamp;
? ? ? ? private Pair(T reference, int stamp) {
? ? ? ? ? ? this.reference = reference;
? ? ? ? ? ? this.stamp = stamp;
? ? ? ? }
? ? ? ? static Pair of(T reference, int stamp) {
? ? ? ? ? ? return new Pair(reference, stamp);
? ? ? ? }
? ? }
? ? //存儲數(shù)值和時間的內(nèi)部類
? ? private volatile Pair pair;
? ? //構(gòu)造器,創(chuàng)建時需傳入初始值和時間初始值
? ? public AtomicStampedReference(V initialRef, int initialStamp) {
? ? ? ? pair = Pair.of(initialRef, initialStamp);
? ? }
}
接著看看其compareAndSet方法的實現(xiàn):
public boolean compareAndSet(V expectedReference,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? V? newReference,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int expectedStamp,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int newStamp) {
? ? ? ? Pair current = pair;
? ? ? ? return? ? ? ? ? ? expectedReference == current.reference &&
? ? ? ? ? ? expectedStamp == current.stamp &&
? ? ? ? ? ? ((newReference == current.reference &&
? ? ? ? ? ? ? newStamp == current.stamp) ||
? ? ? ? ? ? casPair(current, Pair.of(newReference, newStamp)));
? ? }
同時對當(dāng)前數(shù)據(jù)和當(dāng)前時間進(jìn)行比較,只有兩者都相等是才會執(zhí)行casPair()方法幸斥,單從該方法的名稱就可知是一個CAS方法匹摇,最終調(diào)用的還是Unsafe類中的compareAndSwapObject方法
private boolean casPair(Pair cmp, Pair val) {
? ? ? ? return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
到這我們就很清晰AtomicStampedReference的內(nèi)部實現(xiàn)思想了,通過一個鍵值對Pair存儲數(shù)據(jù)和時間戳甲葬,在更新時對數(shù)據(jù)和時間戳進(jìn)行比較廊勃,只有兩者都符合預(yù)期才會調(diào)用Unsafe的compareAndSwapObject方法執(zhí)行數(shù)值和時間戳替換,也就避免了ABA的問題经窖。
AtomicMarkableReference類
AtomicMarkableReference與AtomicStampedReference不同的是坡垫,AtomicMarkableReference維護(hù)的是一個boolean值的標(biāo)識,也就是說至于true和false兩種切換狀態(tài)画侣,經(jīng)過博主測試冰悠,這種方式并不能完全防止ABA問題的發(fā)生,只能減少ABA問題發(fā)生的概率配乱。
public class ABADemo {
?static AtomicMarkableReference atMarkRef =
? ? ? ? ? ? ? new AtomicMarkableReference(100,false);
static Thread t5 = new Thread(new Runnable() {
? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? boolean mark=atMarkRef.isMarked();
? ? ? ? ? ? System.out.println("mark:"+mark);
? ? ? ? ? ? //更新為200
? ? ? ? ? ? System.out.println("t5 result:"+atMarkRef.compareAndSet(atMarkRef.getReference(), 200,mark,!mark));
? ? ? ? }
? ? });
? ? static Thread t6 = new Thread(new Runnable() {
? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? boolean mark2=atMarkRef.isMarked();
? ? ? ? ? ? System.out.println("mark2:"+mark2);
? ? ? ? ? ? System.out.println("t6 result:"+atMarkRef.compareAndSet(atMarkRef.getReference(), 100,mark2,!mark2));
? ? ? ? }
? ? });
? ? static Thread t7 = new Thread(new Runnable() {
? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? boolean mark=atMarkRef.isMarked();
? ? ? ? ? ? System.out.println("sleep 前 t7 mark:"+mark);
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? TimeUnit.SECONDS.sleep(1);
? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? }
? ? ? ? ? ? boolean flag=atMarkRef.compareAndSet(100,500,mark,!mark);
? ? ? ? ? ? System.out.println("flag:"+flag+",newValue:"+atMarkRef.getReference());
? ? ? ? }
? ? });
? ? public static? void? main(String[] args) throws InterruptedException {? ? ? ?
? ? ? ? t5.start();t5.join();
? ? ? ? t6.start();t6.join();
? ? ? ? t7.start();
? ? ? ? /**
? ? ? ? * 輸出結(jié)果:
? ? ? ? mark:false
? ? ? ? t5 result:true
? ? ? ? mark2:true
? ? ? ? t6 result:true
? ? ? ? sleep 前 t5 mark:false
? ? ? ? flag:true,newValue:500 ---->成功了.....說明還是發(fā)生ABA問題
? ? ? ? */? ??
}
}
AtomicMarkableReference的實現(xiàn)原理與AtomicStampedReference類似溉卓,這里不再介紹。到此搬泥,我們也明白了如果要完全杜絕ABA問題的發(fā)生的诵,我們應(yīng)該使用AtomicStampedReference原子類更新對象,而對于AtomicMarkableReference來說只能減少ABA問題的發(fā)生概率佑钾,并不能杜絕。
再談自旋鎖
自旋鎖是一種假設(shè)在不久將來烦粒,當(dāng)前的線程可以獲得鎖休溶,因此虛擬機會讓當(dāng)前想要獲取鎖的線程做幾個空循環(huán)(這也是稱為自旋的原因),在經(jīng)過若干次循環(huán)后扰她,如果得到鎖兽掰,就順利進(jìn)入臨界區(qū)。如果還不能獲得鎖徒役,那就會將線程在操作系統(tǒng)層面掛起孽尽,這種方式確實也是可以提升效率的。但問題是當(dāng)線程越來越多競爭很激烈時忧勿,占用CPU的時間變長會導(dǎo)致性能急劇下降杉女,因此Java虛擬機內(nèi)部一般對于自旋鎖有一定的次數(shù)限制,可能是50或者100次循環(huán)后就放棄鸳吸,直接掛起線程熏挎,讓出CPU資源。如下通過AtomicReference可實現(xiàn)簡單的自旋鎖晌砾。
public class SpinLock{
?????private AtomicReference sign = new AtomicReference<>();
? ????public void lock() {
? ????????? Thread current = Thread.currentThread();
? ? ????????while(!sign.compareAndSet(null, current)){
? ? ????????}
? ????}
? ????public void unlock () {
? ? ????????Thread current = Thread.currentThread();
? ? ????????sign .compareAndSet(current, null);
? ????}
}
使用CAS原子操作作為底層實現(xiàn)坎拐,lock()方法將要更新的值設(shè)置為當(dāng)前線程,并將預(yù)期值設(shè)置為null。unlock()函數(shù)將要更新的值設(shè)置為null哼勇,并預(yù)期值設(shè)置為當(dāng)前線程都伪。然后我們通過lock()和unlock來控制自旋鎖的開啟與關(guān)閉,注意這是一種非公平鎖积担。事實上AtomicInteger(或者AtomicLong)原子類內(nèi)部的CAS操作也是通過不斷的自循環(huán)(while循環(huán))實現(xiàn)陨晶,不過這種循環(huán)的結(jié)束條件是線程成功更新對于的值,但也是自旋鎖的一種磅轻。
ok~珍逸,到此關(guān)于無鎖并發(fā)的知識點暫且了解到這,本篇到此告一段落聋溜。