Java-多線程-Atomic&Unsafe魔術(shù)類

一、原子操作

Java中可以通過鎖和循環(huán)CAS的方式來實現(xiàn)原子操作型凳。JVM中的CAS操作正是利用了上文中提到的處理器提供的CMPXCHG指令實現(xiàn)的。自旋CAS實現(xiàn)的基本思路就是循環(huán)進行CAS操作直到成功為止腮恩,具體的類可以參見juc下的atomic包內(nèi)的原子類峰尝。
原子操作是針對CPU來說的,是一個不可能再分割的一個操作衬吆,要么不執(zhí)行梁钾,要么執(zhí)行完畢。CAS的鎖就是操作系統(tǒng)的緩存行鎖或者總線鎖逊抡。

多個CPU對主內(nèi)存同一塊緩存行進行CAS操作時姆泻,會遵循操作系統(tǒng)的緩存行一致性協(xié)議MESI,基于處理器提供的CMPXCHG指令冒嫡,CPU會把變量讀取到CPU寄存器中進行比較與交換操作(也就是CAS操作)拇勃,優(yōu)先操作完的CPU會把緩存行鎖住并修改緩存行為修改M狀態(tài),其它CPU變?yōu)闊o效I孝凌。操作失敗的CPU會等待操作成功的CPU把值寫回主存內(nèi)方咆,這也是為什么CAS要通過循環(huán)來實現(xiàn)原子操作

注意:一個原子操作在Java中是通過CAS操作實現(xiàn)蟀架,那么一系列的原子操作是怎么實現(xiàn)的瓣赂?通過同步塊加鎖就可以實現(xiàn)。

二片拍、Atomic

在Atomic包里一共有12個類煌集,四種原子更新方式,分別是原子更新基本類型捌省,原子更新數(shù)組牙勘,原子更新引用和原子更新字段。Atomic包里的類基本都是使用Unsafe實現(xiàn)的包裝類所禀。

  • 基本類:AtomicInteger方面、AtomicLong、AtomicBoolean色徘;
  • 引用類型:AtomicReference恭金、AtomicReference的ABA實例、AtomicStampedRerence褂策、AtomicMarkableReference横腿;
  • 數(shù)組類型:AtomicIntegerArray颓屑、AtomicLongArray、AtomicReferenceArray耿焊;
  • 屬性原子修改器:AtomicIntegerFieldUpdater揪惦、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater

應(yīng)用

創(chuàng)建相應(yīng)的操作類型罗侯,傳入初始值器腋。

  public static void main(String[] args) throws InterruptedException {
    AtomicInteger atomicInteger = new AtomicInteger(1);
    for(int i = 0; i<100; i++)
    {
        new Thread(new Runnable() {
            @Override
            public void run() {
                atomicInteger.incrementAndGet();
            }
        }).start();
    }

    Thread.sleep(1000);
    System.out.println("原子操作結(jié)果---->"+atomicInteger.get());
  }

原子操作ABA問題

多線程同時操作同一個AtomicInteger時,比如T1 T2同時操作AtomicInteger atomic時钩杰,atomic初始值為2纫塌,T1 先讀取atomic,值為2讲弄,這是T2進來措左,多次修改了atomic,先 2-->1避除,再 1-->2怎披,T2結(jié)束,T1繼續(xù)往下瓶摆,把atomic修改了凉逛,而且修改成功了。這就是ABA問題赏壹,問題主要出現(xiàn)在T2先執(zhí)行,期間T1修改了atomic而T2卻不知道衔沼。

JUC包中AtomicReference可以解決AtomicInteger ABA的問題蝌借,原理就是引進了版本這一概念,atomic每修改一次指蚁,版本就變一次菩佑。這樣,T1修改了atomic后T2能感知的到凝化。

private static AtomicStampedReference<Integer> atomicStampedRef =
        new AtomicStampedReference<>(1, 0);
public static void main(String[] args){
    Thread main = new Thread(() -> {
        int stamp = atomicStampedRef.getStamp(); //獲取當前標識別
        System.out.println("操作線程" + Thread.currentThread()+ "stamp="+stamp + ",初始值 a = " + atomicStampedRef.getReference());
        try {
            Thread.sleep(1000); //等待1秒 稍坯,以便讓干擾線程執(zhí)行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        boolean isCASSuccess = atomicStampedRef.compareAndSet(1,2,stamp,stamp +1);  //此時expectedReference未發(fā)生改變,但是stamp已經(jīng)被修改了,所以CAS失敗
        System.out.println("操作線程" + Thread.currentThread() + "stamp="+stamp + ",CAS操作結(jié)果: " + isCASSuccess);
    },"主操作線程");

    Thread other = new Thread(() -> {
        int stamp = atomicStampedRef.getStamp();
        atomicStampedRef.compareAndSet(1,2,stamp,stamp+1);
        System.out.println("操作線程" + Thread.currentThread() + "stamp="+atomicStampedRef.getStamp() +",【increment】 ,值 = "+ atomicStampedRef.getReference());
        stamp = atomicStampedRef.getStamp();
        atomicStampedRef.compareAndSet(2,1,stamp,stamp+1);
        System.out.println("操作線程" + Thread.currentThread() + "stamp="+atomicStampedRef.getStamp() +",【decrement】 ,值 = "+ atomicStampedRef.getReference());
    },"干擾線程");

    main.start();
    other.start();
}

三搓劫、Unsafe魔術(shù)類

Unsafe是位于sun.misc包下的一個類瞧哟,主要提供一些用于執(zhí)行低級別、不安全操作的方法枪向,如直接訪問系統(tǒng)內(nèi)存資源勤揩、自主管理內(nèi)存資源等,這些方法在提升Java運行效率秘蛔、增強Java語言底層資源操作能力方面起到了很大的作用陨亡。但由于Unsafe類使Java語言擁有了類似C語言指針一樣操作內(nèi)存空間的能力傍衡,這無疑也增加了程序發(fā)生相關(guān)指針問題的風險。在程序中過度负蠕、不正確使用Unsafe類會使得程序出錯的概率變大蛙埂,使得Java這種安全的語言變得不再“安全”,因此對Unsafe的使用一定要慎重遮糖。

image.png

Unsafe類為一單例實現(xiàn)绣的,提供靜態(tài)方法getUnsafe獲取Unsafe實例,當且僅當調(diào)用getUnsafe方法的類為引導(dǎo)類加載器所加載時才合法止吁,否則拋出SecurityException異常被辑。

如何獲取Unsafe實例?

1敬惦、從getUnsafe方法的使用限制條件出發(fā)盼理,通過Java命令行命令-Xbootclasspath/a把調(diào)用Unsafe相關(guān)方法的類A所在jar包路徑追加到默認的bootstrap路徑中,使得A被引導(dǎo)類加載器加載俄删,從而通過Unsafe.getUnsafe方法安全的獲取Unsafe實例宏怔。

2、通過反射獲取單例對象theUnsafe畴椰。

import sun.misc.Unsafe;
import java.lang.reflect.Field;
public class UnsafeInstance {
    public static Unsafe reflectGetUnsafe() {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            return (Unsafe) field.get(null);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}
Unsafe對象中使用CAS
public class AtomicStudentAgeUpdater {
    private String name ;
    private volatile int age;

    private static final Unsafe unsafe = UnsafeInstance.reflectGetUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset(AtomicStudentAgeUpdater.class.getDeclaredField("age"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }

    public void compareAndSwapAge(int old,int target){
        unsafe.compareAndSwapInt(this,valueOffset,old,target);
    }

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

    public int getAge(){
        return this.age;
    }

    public static void main(String[] args) {
        AtomicStudentAgeUpdater updater = new AtomicStudentAgeUpdater("張三",18);
        updater.compareAndSwapAge(18,17);
    }
}
Unsafe線程調(diào)度
 static Object object = new Object();
 Unsafe unsafe = UnsafeInstance.reflectGetUnsafe();
 //加鎖解鎖
 unsafe.monitorEnter(object);
 //同步塊
 unsafe.monitorExit(object);
 //線程阻塞臊诊、喚醒
 unsafe.park();
 unsafe.unpark();
Unsafe內(nèi)存屏障,防止指令重排
UnsafeInstance.reflectGetUnsafe().loadFence();//讀屏障
UnsafeInstance.reflectGetUnsafe().storeFence();//寫屏障
UnsafeInstance.reflectGetUnsafe().fullFence();//讀寫屏障
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末斜脂,一起剝皮案震驚了整個濱河市抓艳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌帚戳,老刑警劉巖玷或,帶你破解...
    沈念sama閱讀 212,222評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異片任,居然都是意外死亡偏友,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評論 3 385
  • 文/潘曉璐 我一進店門对供,熙熙樓的掌柜王于貴愁眉苦臉地迎上來位他,“玉大人,你說我怎么就攤上這事产场《焖瑁” “怎么了?”我有些...
    開封第一講書人閱讀 157,720評論 0 348
  • 文/不壞的土叔 我叫張陵京景,是天一觀的道長迈勋。 經(jīng)常有香客問我,道長醋粟,這世上最難降的妖魔是什么靡菇? 我笑而不...
    開封第一講書人閱讀 56,568評論 1 284
  • 正文 為了忘掉前任重归,我火速辦了婚禮,結(jié)果婚禮上厦凤,老公的妹妹穿的比我還像新娘鼻吮。我一直安慰自己,他們只是感情好较鼓,可當我...
    茶點故事閱讀 65,696評論 6 386
  • 文/花漫 我一把揭開白布椎木。 她就那樣靜靜地躺著,像睡著了一般博烂。 火紅的嫁衣襯著肌膚如雪香椎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,879評論 1 290
  • 那天禽篱,我揣著相機與錄音畜伐,去河邊找鬼。 笑死躺率,一個胖子當著我的面吹牛玛界,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播悼吱,決...
    沈念sama閱讀 39,028評論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼慎框,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了后添?” 一聲冷哼從身側(cè)響起笨枯,我...
    開封第一講書人閱讀 37,773評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎遇西,沒想到半個月后馅精,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,220評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡努溃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,550評論 2 327
  • 正文 我和宋清朗相戀三年硫嘶,在試婚紗的時候發(fā)現(xiàn)自己被綠了阻问。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片梧税。...
    茶點故事閱讀 38,697評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖称近,靈堂內(nèi)的尸體忽然破棺而出第队,到底是詐尸還是另有隱情,我是刑警寧澤刨秆,帶...
    沈念sama閱讀 34,360評論 4 332
  • 正文 年R本政府宣布凳谦,位于F島的核電站,受9級特大地震影響衡未,放射性物質(zhì)發(fā)生泄漏尸执。R本人自食惡果不足惜家凯,卻給世界環(huán)境...
    茶點故事閱讀 40,002評論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望如失。 院中可真熱鬧绊诲,春花似錦、人聲如沸褪贵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脆丁。三九已至世舰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間槽卫,已是汗流浹背跟压。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留晒夹,地道東北人裆馒。 一個月前我還...
    沈念sama閱讀 46,433評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像丐怯,于是被迫代替她去往敵國和親喷好。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,587評論 2 350

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