java併發知識

進程:是開闢運行程序空間環境的,系統進行資源分配和調度的基本單位。一個應用在啓動時會像電腦申請一塊兒內存以便運行自己的程序,內存中有堆有棧有方法區,用來儲存資源。

線程:是用來執行程序命令的,所有線程可共享所屬進程資源,線程是依賴進程存在的,無法獨立存在。一個進程下允許存在N條線程,線程的創建和銷燬都是在程序中靈活控制的,爲了程序運行快就需要多條線程去執行不同的命令。

併發:指多個線程同時在執行並且會發生阻塞情況。例子:有1座橋,平時過橋30秒,高峯期人擠人過橋60秒,因爲橋的吞吐量小於人數,所以發生人擠人的情況最終影響大家過橋的速度。大家都在擁擠過橋互相干擾。

並行:指多個線程同事執行但不會阻塞。例子:有10座橋,平時過橋30秒,高峯期因爲橋多不會擁擠所以也是30秒,因爲橋的吞吐量始終和人數成正比。大家都在平行過橋互不干擾。

同步/原子性:當一個資源被多個線程爭相寫入修改時,需要有先後順序的修改寫入,這種有秩序的行爲稱爲同步。

異步:給一條線程下發命令去運行某個方法,什麼時候運行完不關心,返回值也不關心,只管讓其去運行即可。一般用於日誌記錄,消息派發等等功能。

CPU:用來運算數據產生結果的。其中包含:

  寄存器(一級緩存):用來存儲計算結果的。

  運算器:正真運算的,運算的結果會保存到寄存器中。

  控制器:當運算器運算完結果保存到寄存器中後負責將結果刷新到二級緩存(多cup共用的緩存,若只有一個cup就不會有二級緩存,只有主內存和寄存器),再刷新到主內存。

內存不可見:假如有2個CPU,分別是A,B。A更新值x; 會先去寄存器中找x,找不到再去二級緩存中找,仍然沒有就直接去主內存中找,若沒有則創建。找到後賦值爲x= 1;然後依次刷新到主內存(寄存器 -> 二級緩存 -> 主內存)。

      此時B也更新X,最終在二級緩存中找到x = 1,然後更新爲x = 2,之後由寄存在 -> 二級緩存 -> 主內存(刷新結果),此時結果是:A寄存器x = 1. B寄存器x = 2, 二級緩存x= 2, 主內存x= 2;.

      發現結果了吧:A和B的寄存器x數據不一致,這就是內存不可見。內存不可見就會導致數據錯誤。

內存可見性/關鍵字:volatile:底層數據一經修改所有獲取此數據的人都會第一時間知道。爲了解決內存不可見java提供了volatile關鍵字,使用此關鍵字描述變量,就會在變量發生變化的第一時間系統會通知所有內存此變量的最新值,以達到內存可見的目的。  

關鍵字:synchronized:作用是保證同步並且擁有可見性。底層原理是:當某個線程獲取到它時:它的計數器+1,然後記錄是哪條線程佔用了它。並且它時可重入鎖,就是在synchronized方法中還可以訪問synchronized只不過計數器還會加1,當程序執行完畢後計數器清0,記錄信息銷燬,此時其他線程就可搶奪了。

CAS:CAS即Compare and Swap,其是JDK提供的非阻塞原子性操作,它通過硬件保證了比較—更新操作的原子性。JDK裏面的Unsafe類提供了一系列的compareAndSwap*方法.。包括redis中也提供了一些ACS操作,就是給出一個期待值,再給出一個目標值。當期待值符合預期就會變爲目標值並返回true,反之不變返回false.如:RedisAtomicLong的機制,自增1,原子性操作,更多例子參考:https://www.cnblogs.com/wuyx/p/10480349.html

    boolean compareAndSwapLong(Object obj, long valueOffset, longexpect, long update)方法:其中compareAndSwap的意思是比較並交換。

    CAS有四個操作數,分別爲:對象內存位置、對象中的變量的偏移量、變量預期值和新的值。其操作含義是,如果對象obj中內存偏移量爲valueOffset的變量值爲expect,則使用新的值update替換舊的值expect。這是處理器提供的一個原子性指令。

    JDK的rt.jar包中的Unsafe類提供了硬件級別的原子性操作,Unsafe類中的方法都是native方法,它們使用JNI的方式訪問本地C++ 實現庫。   

package mr.li.test.util;

import java.lang.reflect.Field;

import org.springframework.objenesis.instantiator.util.UnsafeUtils;
import org.springframework.stereotype.Component;

import sun.misc.Unsafe;

@SuppressWarnings("restriction")
@Component
public class TestUnsafe {

    static Unsafe unsafe;
    static long stateOffset;
    private int code2;
    private long code1;

    static {
        try {
            // 使用反射獲取Unsafe的成員變量theUnsafe
            Field fieId = Unsafe.class.getDeclaredField("theUnsafe");
            // 設置爲可存取
            fieId.setAccessible(true);
            // 獲取該變量的值
            unsafe = (Unsafe) fieId.get(null);
            // 獲取student的code在在TestUtil中的偏移量
            stateOffset = unsafe.objectFieldOffset(TestUnsafe.class.getDeclaredField("code1"));
        } catch (Exception e) {
            System.out.println(e.getLocalizedMessage());
            throw new Error(e);
        }
    }

    public static void main(String[] args) throws NoSuchFieldException, SecurityException {

        // 方式一獲取Unsafe:採用反射獲取,因爲這個方法比較敏感所以jdk不會讓我們輕易調用。java14以後這種反射調用私有方法的行爲不可行了,被限制了。這裏用的是java8
        TestUnsafe testUnsafe1 = new TestUnsafe();
        testUnsafe1.setCode1(222L);
        boolean is2 = unsafe.compareAndSwapLong(testUnsafe1, stateOffset, 222L, 55555L);
        System.out.println("方式1:Long類型ACS操作" + is2 + " -> " + testUnsafe1.getCode1());// 方式1:true -> 55555

        // ----------------------------------------------------------------------------------------------------

        // 方式二獲得Unsafe:使用spring提供的工具包獲取。
        Unsafe unsafe1 = UnsafeUtils.getUnsafe();
        TestUnsafe testUnsafe2 = new TestUnsafe();
        testUnsafe2.setCode2(1112);
        // 獲取Student中的code在TestUtil中的偏移量
        long offset = unsafe1.objectFieldOffset(TestUnsafe.class.getDeclaredField("code2"));
        // 操作對象,偏移量, 原來的值, 需要變更新的值
        boolean is = unsafe1.compareAndSwapInt(testUnsafe2, offset, 1112, 222);
        System.out.println("方式2:int類型ACS操作:" + is + " -> " + testUnsafe2.getCode2());// 方式2:true -> 222
    }

    public int getCode2() {
        return code2;
    }

    public void setCode2(int code2) {
        this.code2 = code2;
    }

    public long getCode1() {
        return code1;
    }

    public void setCode1(long code1) {
        this.code1 = code1;
    }
}

僞共享:以拷貝主內存數據到各自緩存已到達各個線程數據一致的情況我們稱之爲僞共享。爲了解決計算機系統中主內存與cpu之間運行速度差問題,會在cpu和主內存之間加入一些高速緩衝儲存器(cache)。通俗將就是爲了快不讓cup做一些無用功,就會將計算結果儲存在緩存器中,當下次有同樣的計算需求時直接從緩存中拿結果而不用去計算。讓好鋼用在刀刃上。但是因爲1級緩存(上面說的寄存器),二級緩存都屬於拷貝的主內存結果,所以他們自己運算後纔會把結果刷新到主內存,緩存並不是主內存所以看似所有線程數據一致,其實都是拷貝的主內存的結果。這種情況我們稱之爲“僞共享”。

鎖的類型介紹,下面的介紹都是鎖的特性,一種鎖可能佔好幾個特性例如synchronized擁有:悲觀,非公平,獨佔,可重入等特性

悲觀鎖:要訪問數據就要排隊。像:synchronized就是悲觀鎖。悲觀鎖起源於數據庫。

樂觀鎖:先執行程序當最後提交時看看數據是不是預期值,若是:成功,不是:回滾,掛起或輪詢。有點CAS的意思.樂觀鎖的主要思想是,並沒有那麼多的併發,運氣不好我就回滾,但直接成功(沒人和我爭)的可能性很大。

公平鎖:先來後到順序搶佔資源,ReentrantLock pairLock = new ReentrantLock(true)。

非公平鎖:一旦鎖資源釋放各位各憑本事搶資源,誰搶到就誰的其他人繼續等着去。ReentrantLock pairLock = new ReentrantLock(false)一般默認是非公平鎖,因爲公平鎖還需要線程額外維護,若非必要就是浪費性能。

獨佔鎖:見名知意,自己將鎖佔住只要釋放了其他人才能擁有。ReentrantLock, synchronized

共享鎖:大家都可以擁有鎖資源,例如:某個文件的讀就是共享鎖:大家在不改變資源的前提下都可以讀,但不可以寫。ReadWriteLock,共享鎖也是一種樂觀鎖。

可重入鎖:允許在佔有鎖資源的前提下又去搶佔鎖資源,synchronized就是可重入鎖。

自旋鎖:當未搶到資源是就會輪詢不停搶,知道一定次數後還沒搶到則會掛起。

 

AtomicLong,AtomicInt,AtomicBoolean都是jdk提提供的原子性操作類,底層使用的是Unsafe來實現,這種CAS方式就是大家一起來自增,只有一個自增成功其他的都自旋,知道自己搶到爲止。這種特點是性能要比鎖好很多,缺點就是性能開銷大。所以java又提供了一種用於高併發下的原子操作類,jdk8提供:LongAdder.

AtomicLong示意圖:

 

使用AtomicLong時,是多個線程同時競爭同一個原子變量。

 

LongAdder示意圖:

 

 使用LongAdder時,則是在內部維護多個Cell變量,每個Cell裏面有一個初始值爲0的long型變量,這樣,在同等併發量的情況下,爭奪單個變量更新操作的線程量會減少,這變相地減少了爭奪共享資源的併發量。另外,多個線程在爭奪同一個Cell原子變量時如果失敗了,它並不是在當前Cell變量上一直自旋CAS重試,而是嘗試在其他Cell的變量上進行CAS嘗試,這個改變增加了當前線程重試CAS成功的可能性。最後,在獲取LongAdder當前值時,是把所有Cell變量的value值累加後再加上base返回的。LongAdder維護了一個延遲初始化的原子性更新數組(默認情況下Cell數組是null)和一個基值變量base。由於Cells佔用的內存是相對比較大的,所以一開始並不創建它,而是在需要時創建,也就是惰性加載。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章