Java并發(fā)編程 -- Atomic包

Java從JDK1.5開始提供了java.util.concurrent.atomic包,方便程序員在多線程環(huán)境下,無鎖的進行原子操作。原子變量的底層使用了處理器提供的原子指令,但是不同的CPU架構(gòu)可能提供的原子指令不一樣,也有可能需要某種形式的內(nèi)部鎖,所以該方法不能絕對保證線程不被阻塞。

Atomic包介紹

官方解釋:一個小型工具包,支持單變量上的無鎖線程安全編程。


image.png

可以看到,它里面有17個java類。

原子更新基本類型類

用于通過原子的方式更新基本類型,Atomic包提供了以下三個類:

  • AtomicBoolean:原子更新布爾類型。
  • AtomicInteger:原子更新整型。
  • AtomicLong:原子更新長整型。

AtomicInteger的常用方法如下:

  • int addAndGet(int delta) :以原子方式將輸入的數(shù)值與實例中的值(AtomicInteger里的value)相加,并返回結(jié)果
  • boolean compareAndSet(int expect, int update) :如果輸入的數(shù)值等于預(yù)期值,則以原子方式將該值設(shè)置為輸入的值。
  • int getAndIncrement():以原子方式將當前值加1,注意:這里返回的是自增前的值。
  • void lazySet(int newValue):最終會設(shè)置成newValue,使用lazySet設(shè)置值后,可能導致其他線程在之后的一小段時間內(nèi)還是可以讀到舊的值。關(guān)于該方法的更多信息可以參考并發(fā)網(wǎng)翻譯的一篇文章《AtomicLong.lazySet是如何工作的?
  • int getAndSet(int newValue):以原子方式設(shè)置為newValue的值,并返回舊值。
AtomicInteger實例
package com.thread.atomic;

import java.util.concurrent.atomic.AtomicInteger;

/**
 * Created by Fant.J.
 * 2018/2/28 17:21
 */
public class AtomicIntegerTest {
    static AtomicInteger value = new AtomicInteger(0);

    public static int getValue(){
        return value.getAndIncrement();//value++;
    }

    public static void main(String[] args) {

  /*      Runnable r1 = ()->{
            for (;;){
                System.out.println(Thread.currentThread().getName()+" :"+getValue());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        r1.run();*/

        new Thread(()->{
            for (;;){
                System.out.println(Thread.currentThread().getName()+" :"+getValue());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(()->{
            for (;;){
                System.out.println(Thread.currentThread().getName()+" :"+getValue());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

    }
}

用的lambda表達式,不懂的可以看我的文章:http://www.itdecent.cn/p/3a08dc78a05f

餐后甜點

Atomic包提供了三種基本類型的原子更新,但是Java的基本類型里還有char,float和double等。那么問題來了,如何原子的更新其他的基本類型呢?Atomic包里的類基本都是使用Unsafe實現(xiàn)的,讓我們一起看下Unsafe的源碼,發(fā)現(xiàn)Unsafe只提供了三種CAS方法,compareAndSwapObject,compareAndSwapInt和compareAndSwapLong,再看AtomicBoolean源碼,發(fā)現(xiàn)其是先把Boolean轉(zhuǎn)換成整型,再使用compareAndSwapInt進行CAS,所以原子更新double也可以用類似的思路來實現(xiàn)。

原子更新數(shù)組類

通過原子的方式更新數(shù)組里的某個元素,Atomic包提供了以下三個類:

  • AtomicIntegerArray:原子更新整型數(shù)組里的元素。

  • AtomicLongArray:原子更新長整型數(shù)組里的元素。

  • AtomicReferenceArray:原子更新引用類型數(shù)組里的元素。

  • AtomicIntegerArray類主要是提供原子的方式更新數(shù)組里的整型,其常用方法如下

  • int addAndGet(int i, int delta):以原子方式將輸入值與數(shù)組中索引i的元素相加。

  • boolean compareAndSet(int i, int expect, int update):如果當前值等于預(yù)期值,則以原子方式將數(shù)組位置i的元素設(shè)置成update值。

示例:

public class AtomicIntegerArrayTest {
    static int[] value = new int[]{1,2};
    static AtomicIntegerArray ai = new AtomicIntegerArray(value);

    public static void main(String[] args) {
        ai.getAndSet(0,3);
        System.out.println(ai.get(0));
    }
}

原子更新引用類型

原子更新基本類型的AtomicInteger,只能更新一個變量,如果要原子的更新多個變量,就需要使用這個原子更新引用類型提供的類。Atomic包提供了以下三個類:

AtomicReference:原子更新引用類型。
AtomicReferenceFieldUpdater:原子更新引用類型里的字段。
AtomicMarkableReference:原子更新帶有標記位的引用類型。可以原子的更新一個布爾類型的標記位和引用類型。構(gòu)造方法是AtomicMarkableReference(V initialRef, boolean initialMark)

AtomicReference的使用例子代碼如下:

package com.thread.atomic;

import java.util.concurrent.atomic.AtomicReference;

/**
 * Created by Fant.J.
 * 2018/2/28 18:40
 */
public class AtomicReferenceTest {
    static AtomicReference<User> ar = new AtomicReference<>();

    public static void main(String[] args) {
        User user = new User("fantj",20);
        User updateUser = new User("dalao",20);

        ar.set(user);
        ar.compareAndSet(user,updateUser); //public final boolean compareAndSet(User expect, User update)
        System.out.println(ar.get().getName() + " :"+ar.get().getAge());
    }


    static class User{
        private String name;

        private Integer age;

        public User(String name, int i) {
            this.name = name;
            this.age=i;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }
    }
}

原子更新字段類

如果我們只需要某個類里的某個字段,那么就需要使用原子更新字段類,Atomic包提供了以下三個類:

  • AtomicIntegerFieldUpdater:原子更新整型的字段的更新器。
  • AtomicLongFieldUpdater:原子更新長整型字段的更新器。
  • AtomicStampedReference:原子更新帶有版本號的引用類型。該類將整數(shù)值與引用關(guān)聯(lián)起來,可用于原子的更數(shù)據(jù)和數(shù)據(jù)的版本號,可以解決使用CAS進行原子更新時,可能出現(xiàn)的ABA問題。

原子更新字段類都是抽象類,每次使用都時候必須使用靜態(tài)方法newUpdater創(chuàng)建一個更新器。原子更新類的字段的必須使用public volatile修飾符。

AtomicIntegerFieldUpdater的例子代碼如下:

package com.thread.atomic;

import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;

/**
 * 如果我們只需要某個類里的某個字段,那么就需要使用原子更新字段類
 * 原子更新類的字段的必須使用public volatile修飾符。
 * Created by Fant.J.
 * 2018/2/28 18:50
 */
public class AtomicIntegerFieldUpdaterTest {

    //假設(shè)我們需要User里的age屬性,并給age進行原子更新
    static AtomicIntegerFieldUpdater<User> aif = AtomicIntegerFieldUpdater.newUpdater(User.class,"age");

    public static void main(String[] args) {
        User user = new User("fantj",20);
        //將User對象傳給 原子更新字段類對象
        aif.getAndIncrement(user);
        System.out.println(aif.get(user));
    }




    static class User{
        private String name;

        public volatile int age;   //注意這里要加 volatile

        public User(String name, int i) {
            this.name = name;
            this.age=i;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public Integer getAge() {
            return age;
        }

        public void setAge(Integer age) {
            this.age = age;
        }
    }
}

那最后,再來解一下CAS的疑惑

什么是CAS

CAS,Compare and Swap即比較并交換。 java.util.concurrent包借助CAS實現(xiàn)了區(qū)別于synchronized同步鎖的一種樂觀鎖。樂觀鎖就是每次去取數(shù)據(jù)的時候都樂觀的認為數(shù)據(jù)不會被修改,所以不會上鎖,但是在更新的時候會判斷一下在此期間數(shù)據(jù)有沒有更新。CAS有3個操作數(shù):內(nèi)存值V,舊的預(yù)期值A(chǔ),要修改的新值B。當且僅當預(yù)期值A(chǔ)和內(nèi)存值V相同時,將內(nèi)存值V修改為B,否則什么都不做。CAS的關(guān)鍵點在于,系統(tǒng)在硬件層面保證了比較并交換操作的原子性,處理器使用基于對緩存加鎖或總線加鎖的方式來實現(xiàn)多處理器之間的原子操作。

CAS的優(yōu)缺點
  • CAS由于是在硬件層面保證的原子性,不會鎖住當前線程,它的效率是很高的。

  • CAS雖然很高效的實現(xiàn)了原子操作,但是它依然存在三個問題。

    1. ABA問題。CAS在操作值的時候檢查值是否已經(jīng)變化,沒有變化的情況下才會進行更新。但是如果一個值原來是A,變成B,又變成A,那么CAS進行檢查時會認為這個值沒有變化,但是實際上卻變化了。ABA問題的解決方法是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那么A-B-A 就變成1A-2B-3A。從Java1.5開始JDK的atomic包里提供了一個類AtomicStampedReference來解決ABA問題。

    2. 并發(fā)越高,失敗的次數(shù)會越多,CAS如果長時間不成功,會極大的增加CPU的開銷。因此CAS不適合競爭十分頻繁的場景。

    3. 只能保證一個共享變量的原子操作。當對多個共享變量操作時,CAS就無法保證操作的原子性,這時就可以用鎖,或者把多個共享變量合并成一個共享變量來操作。比如有兩個共享變量i=2,j=a,合并一下ij=2a,然后用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象的原子性,你可以把多個變量放在一個對象里來進行CAS操作。

以上只介紹了常用的atomic類,其它的類如果你感興趣可以自己研究一下。官方文檔

本文參考文獻:
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html
http://ifeve.com/java-atomic/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容