(轉(zhuǎn)載)Java并發(fā)編程-并發(fā)包中的原子操作類(Atomic系列)

原文鏈接: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ā)的知識點暫且了解到這,本篇到此告一段落聋溜。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末谆膳,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子撮躁,更是在濱河造成了極大的恐慌漱病,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件把曼,死亡現(xiàn)場離奇詭異杨帽,居然都是意外死亡,警方通過查閱死者的電腦和手機嗤军,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門注盈,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人叙赚,你說我怎么就攤上這事老客。” “怎么了震叮?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵胧砰,是天一觀的道長。 經(jīng)常有香客問我苇瓣,道長尉间,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任击罪,我火速辦了婚禮哲嘲,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘媳禁。我一直安慰自己撤蚊,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布损话。 她就那樣靜靜地躺著侦啸,像睡著了一般槽唾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上光涂,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天庞萍,我揣著相機與錄音,去河邊找鬼忘闻。 笑死钝计,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的齐佳。 我是一名探鬼主播私恬,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼炼吴!你這毒婦竟也來了本鸣?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤硅蹦,失蹤者是張志新(化名)和其女友劉穎荣德,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體童芹,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡涮瞻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了假褪。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片署咽。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖生音,靈堂內(nèi)的尸體忽然破棺而出艇抠,到底是詐尸還是另有隱情,我是刑警寧澤久锥,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站异剥,受9級特大地震影響瑟由,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜冤寿,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一歹苦、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧督怜,春花似錦殴瘦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽丰歌。三九已至,卻和暖如春屉凯,著一層夾襖步出監(jiān)牢的瞬間立帖,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工悠砚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留晓勇,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓灌旧,卻偏偏與公主長得像绑咱,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子枢泰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容