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)指針問題的風(fēng)險。在程序中過度、不正確使用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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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