Java多線程進階(十六)—— J.U.C之atomic框架:FieldUpdater

3.jpg

本文首發於一世流雲的專欄: https://segmentfault.com/blog...

1、什麼是FieldUpdater

java.util.concurrent.atomic包中,由三個比較特殊的原子類:AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicReferenceFieldUpdater
經過名稱能夠看到,這幾類的功能大體相同,只是針對的類型有所不一樣。java

所謂AtomicXXXFieldUpdater,就是能夠以一種線程安全的方式操做非線程安全對象的某些字段。光這麼說有點難理解,咱們經過一個例子來看下。segmentfault

假設有一個公司帳戶Account,100我的同時往裏面存錢1塊錢,那麼正常狀況下,最終帳戶的總金額應該是100。

先來看下線程不安全的方式:安全

帳戶類:併發

class Account {
    private volatile int money;

    Account(int initial) {
        this.money = initial;
    }

    public void increMoney() {
        money++;
    }

    public int getMoney() {
        return money;
    }

    @Override
    public String toString() {
        return "Account{" +
            "money=" + money +
            '}';
    }
}

調用類:框架

public class FieldUpdaterTest {
    public static void main(String[] args) throws InterruptedException {
        Account account = new Account(0);  // 初始金額0

        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            Thread t = new Thread(new Task(account));
            list.add(t);
            t.start();
        }

        for (Thread t : list) {            // 等待全部線程執行完成
            t.join();
        }

        System.out.println(account.toString());
    }


    private static class Task implements Runnable {
        private Account account;

        Task(Account account) {
            this.account = account;
        }

        @Override
        public void run() {
            account.increMoney();         // 增長帳戶金額
        }
    }
}

上述未對Account作併發控制,最終帳戶金額極可能小於100
按照以前學習的atomic框架,能夠將Account類的int類型字段改成AtomicInteger,或者在Task任務類中,將全部涉及到共享變量的地方都加鎖訪問。ide

那麼,還有沒有其它解決方式?學習

本章開頭咱們講到,AtomicXXXFieldUpdater能夠以一種線程安全的方式操做非線程安全對象的某些字段
這裏,Account就是非線程安全對象,money就是須要操做的字段。this

咱們來對上述代碼進行改造:
帳戶類Account改造:atom

class Account {
    private volatile int money;
    private static final AtomicIntegerFieldUpdater<Account> updater = AtomicIntegerFieldUpdater.newUpdater(Account.class, "money");  // 引入AtomicIntegerFieldUpdater

    Account(int initial) {
        this.money = initial;
    }

    public void increMoney() {
        updater.incrementAndGet(this);    // 經過AtomicIntegerFieldUpdater操做字段
    }

    public int getMoney() {
        return money;
    }

    @Override
    public String toString() {
        return "Account{" +
            "money=" + money +
            '}';
    }
}

調用方,並未作任何改變:spa

public class FieldUpdaterTest {
    public static void main(String[] args) throws InterruptedException {
        Account account = new Account(0);

        List<Thread> list = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            Thread t = new Thread(new Task(account));
            list.add(t);
            t.start();
        }

        for (Thread t : list) {
            t.join();
        }

        System.out.println(account.toString());
    }

    private static class Task implements Runnable {
        private Account account;

        Task(Account account) {
            this.account = account;
        }

        @Override
        public void run() {
            account.increMoney();
        }
    }
}

上述代碼,不管執行多少次,最終結果都是「100」,由於這回是線程安全的。

對比下改造,能夠發現,AtomicIntegerFiledUpdater的引入,使得咱們能夠在不修改用戶代碼(調用方)的狀況下,就能實現併發安全性

惟一的改變之處就是Account內部的改造:
clipboard.png

這也是AtomicXXXFieldUpdater引入的一個重要緣由,單純從功能上來說,能用AtomicXXXFieldUpdater實現的併發控制,同步器和其它原子類都能實現,可是使用AtomicXXXFieldUpdater,符合面向對象設計的一個基本原則——開閉原則,尤爲是對一些遺留代碼的改造上。

另外,使用AtomicXXXFieldUpdater,不須要進行任何同步處理,單純的使用CAS+自旋操做就能夠實現同步的效果。這也是整個atomic包的設計理念之一。

2、AtomicReferenceFieldUpdater原理

AtomicIntegerFieldUpdaterAtomicLongFieldUpdaterAtomicReferenceFieldUpdater這三個類大同小異,AtomicIntegerFieldUpdater只能處理int原始類型的字段,AtomicLongFieldUpdater只能處理long原始類型的字段,AtomicReferenceFieldUpdater能夠處理全部引用類型的字段。

本節以AtomicReferenceFieldUpdater爲例,介紹下FiledUpdater的基本原理。

AtomicReferenceFieldUpdater對象的建立

AtomicReferenceFieldUpdater自己是一個抽象類,沒有公開的構造器,只能經過靜態方法newUpdater建立一個AtomicReferenceFieldUpdater子類對象:
clipboard.png

newUpdater的三個入參含義以下:

入參名稱 含義
tclass 目標對象的類型
vclass 目標字段的類型
fieldName 目標字段名

AtomicReferenceFieldUpdaterImpl是AtomicReferenceFieldUpdater的一個內部類,並繼承了AtomicReferenceFieldUpdater。AtomicReferenceFieldUpdater的API,基本都是委託AtomicReferenceFieldUpdaterImpl 來實現的。

來看下AtomicReferenceFieldUpdaterImpl 對象的構造,其實就是一系列的權限檢查:
clipboard.png

經過源碼,能夠看到AtomicReferenceFieldUpdater的使用必須知足如下條件:

  1. AtomicReferenceFieldUpdater只能修改對於它可見的字段,也就是說對於目標類的某個字段field,若是修飾符是private,可是AtomicReferenceFieldUpdater所在的使用類不能看到field,那就會報錯;
  2. 目標類的操做字段,必須用volatile修飾;
  3. 目標類的操做字段,不能是static的;
  4. AtomicReferenceFieldUpdater只適用於引用類型的字段;

AtomicReferenceFieldUpdater的方法原理

AtomicReferenceFieldUpdater中全部的方法都是基於Unsafe類操做,看下最經常使用的方法compareAndSet
clipboard.png

經過偏移量offset獲取字段的地址,而後利用Unsafe進行CAS更新。

其它方法也大同小異,讀者能夠參考Oracle官方文檔和JDK源碼。

AtomicReferenceFieldUpdater接口聲明

clipboard.png

相關文章
相關標籤/搜索