java并發(fā)筆記

進(jìn)程與線(xiàn)程關(guān)系

1慨默、進(jìn)程與線(xiàn)程均是CPU執(zhí)行時(shí)間段的描述。

2弧腥、進(jìn)程是資源分配的基本單位厦取,線(xiàn)程是CPU調(diào)度的基本單位。

3管搪、一個(gè)進(jìn)程里至少有一個(gè)線(xiàn)程虾攻。

4、同一進(jìn)程里的各個(gè)線(xiàn)程可以共享變量更鲁,它們之間的通信稱(chēng)之為線(xiàn)程間通信霎箍。

5、線(xiàn)程可以看作粒度更小的進(jìn)程岁经。

線(xiàn)程的狀態(tài)

1朋沮、NEW:構(gòu)造了thread實(shí)例,但是還沒(méi)有start

2缀壤、RUNNABLE:線(xiàn)程正在運(yùn)行或者正等待被cpu執(zhí)行

3樊拓、BLOCKED:線(xiàn)程調(diào)用synchronized關(guān)鍵字等待獲取monitor鎖

4、WAITING:線(xiàn)程調(diào)用了無(wú)超時(shí)的wait塘慕、join筋夏、park方法

5、TIMED_WAITING:線(xiàn)程調(diào)用了有超時(shí)的wait图呢、sleep条篷、join、parkNanos蛤织、parkUntil方法

6赴叹、TERMINATED:線(xiàn)程終止/完成了運(yùn)行

join和yield

Join是Thread類(lèi)成員方法,該方法作用是當(dāng)前線(xiàn)程等待某個(gè)線(xiàn)程結(jié)束后再繼續(xù)執(zhí)行后續(xù)代碼指蚜。

Yield是Thread類(lèi)靜態(tài)方法乞巧,該方法作用是讓當(dāng)前線(xiàn)程放棄cpu,并等待cpu重新調(diào)度,只能讓給優(yōu)先級(jí)比自己高的摊鸡。

中斷

線(xiàn)程的thread.interrupt()方法是中斷線(xiàn)程绽媒,將會(huì)設(shè)置該線(xiàn)程的中斷狀態(tài)位蚕冬,即設(shè)置為true,中斷的結(jié)果線(xiàn)程是死亡是辕、還是等待新的任務(wù)或是繼續(xù)運(yùn)行至下一步囤热,就取決于這個(gè)程序本身获三。線(xiàn)程進(jìn)入終止?fàn)顟B(tài)有兩種情況:

1.run方法運(yùn)行結(jié)束

2.run方法中拋出異常牌芋。

如果一個(gè)線(xiàn)程處于了阻塞狀態(tài)(如線(xiàn)程調(diào)用了thread.sleep、thread.join松逊、thread.wait躺屁、1.5中的condition.await、以及可中斷的通道上的 I/O 操作方法后可進(jìn)入阻塞狀態(tài))经宏,則在線(xiàn)程在檢查中斷標(biāo)示時(shí)如果發(fā)現(xiàn)中斷標(biāo)示為true犀暑,則會(huì)在這些阻塞方法(sleep、join烁兰、wait耐亏、1.5中的condition.await及可中斷的通道上的 I/O 操作方法)調(diào)用處拋出InterruptedException異常,并且在拋出異常后立即將線(xiàn)程的中斷標(biāo)示位清除沪斟,即重新設(shè)置為false广辰。拋出異常是為了線(xiàn)程從阻塞狀態(tài)醒過(guò)來(lái),并在結(jié)束線(xiàn)程前讓程序員有足夠的時(shí)間來(lái)處理中斷請(qǐng)求主之。

synchronized在獲鎖的過(guò)程中是不能被中斷的择吊,意思是說(shuō)如果產(chǎn)生了死鎖,則不可能被中斷(請(qǐng)參考后面的測(cè)試?yán)樱┎坜取Ecsynchronized功能相似的reentrantLock.lock()方法也是一樣几睛,它也不可中斷的,即如果發(fā)生死鎖粤攒,那么reentrantLock.lock()方法無(wú)法終止所森,如果調(diào)用時(shí)被阻塞,則它一直阻塞到它獲取到鎖為止夯接。但是如果調(diào)用帶超時(shí)的tryLock方法reentrantLock.tryLock(long timeout, TimeUnit unit)焕济,那么如果線(xiàn)程在等待時(shí)被中斷,將拋出一個(gè)InterruptedException異常盔几,這是一個(gè)非常有用的特性晴弃,因?yàn)樗试S程序打破死鎖

thread.isInterrupted() 和 Thread.interrupted()區(qū)別

Thread.interrupted()調(diào)用后會(huì)重置中斷狀態(tài)為false,相當(dāng)于清空了中斷標(biāo)志位。而thread.isInterrupted()卻不會(huì)清空中斷標(biāo)志位肝匆。

中斷的作用

1、將中斷標(biāo)記為設(shè)置為true

2顺献、將掛起的線(xiàn)程喚醒

如何停止線(xiàn)程

1旗国、外部通過(guò)設(shè)置isCancelThread 來(lái)標(biāo)記是否讓線(xiàn)程停止運(yùn)行。這種寫(xiě)法有個(gè)缺陷注整,若是線(xiàn)程在調(diào)用doSomething1() 或doSomething2()時(shí)阻塞能曾,那么將不會(huì)立即檢測(cè)isCancelThread的值,也即是不能立即停止線(xiàn)程肿轨。

public class TestThread {
    static volatile boolean isCancelThread = false;
    public static void main(String args[]) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                while(!isCancelThread || !Thread.currentThread().isInterrupted()) {
                    try {
                        doSomething1();
                        doSomething2();
                    } catch (Exception e) {
                        if (e instanceof InterruptedException) {
                            isCancelThread = true;
                        }
                    }
                }
            }
        });

        t1.start();

        //另一個(gè)線(xiàn)程兩種方式停止線(xiàn)程
        //1寿冕、設(shè)置isCancelThread = true
        //2、調(diào)用t1.interrupt()
    }

    private static void doSomething1() {
        //具體邏輯
    }

2椒袍、針對(duì)doSomething1() 或doSomething2()可能阻塞的問(wèn)題驼唱,外部通過(guò)使用
Thread.interrupt()中斷線(xiàn)程,此時(shí)需要捕獲異常驹暑,捕獲到了中斷異常意味著可以停止線(xiàn)程運(yùn)行了玫恳。

while(!Thread.currentThread().isInterrupted()) {
        try {
            doSomething1();
            doSomething2();
        } catch (Exception e) {
            if (e instanceof InterruptedException) {
                Thread.currentThread().interrupt();
            }
        }
    }
Volatile

線(xiàn)程并發(fā)三要素:原子性,可見(jiàn)性优俘,有序性

new Thread(() -> {
            int oldValue = sharedVariable;
            while (sharedVariable < MAX) {
                if (sharedVariable != oldValue) {
                    System.out.println(Thread.currentThread().getName() + " watched the change : " + oldValue + "->" + sharedVariable);
                    oldValue = sharedVariable;
                }
            }
            System.out.println(Thread.currentThread().getName() + " stop run");
        }, "t1").start();

        new Thread(() -> {
            int oldValue = sharedVariable;
            while (sharedVariable < MAX) {
                System.out.println(Thread.currentThread().getName() + " do the change : " + sharedVariable + "->" + (++oldValue));
                sharedVariable = oldValue;
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + " stop run");
        }, "t2").start();

結(jié)果發(fā)現(xiàn):

t2將sharedVariable改變?yōu)?0京办,t1只有一次打印

t2線(xiàn)程停止,t1未停止帆焕,程序沒(méi)有停止運(yùn)行

但是在t1里增加打印或者sleep惭婿,能夠讓t1監(jiān)測(cè)到sharedVariable的變化

造成這種原因是:JIT發(fā)現(xiàn)這里是重復(fù)判斷“sharedVariable != oldValue”,因此優(yōu)化了代碼:只在第一次取sharedVariable的值叶雹,后續(xù)不再取最新值财饥,然后一直死循環(huán)浑娜,最終導(dǎo)致程序沒(méi)退出佑力。而增加了println或者sleep后,JIT取消了優(yōu)化筋遭。

cpu實(shí)現(xiàn)了緩存一致性協(xié)議打颤,盡可能保證cache之間數(shù)據(jù)一致性。但是為了充分利用cpu性能漓滔,增加了Store Buffer和Invalidate Queue緩存编饺,導(dǎo)致cpu可能產(chǎn)生指令順序不一致問(wèn)題,看起來(lái)像是指令重排了响驴,通過(guò)增加內(nèi)存讀寫(xiě)屏障來(lái)規(guī)避此問(wèn)題透且。

MESI通過(guò)加入內(nèi)存屏障指令來(lái)確保數(shù)據(jù)順序一致性,那么這個(gè)內(nèi)存屏障是什么時(shí)候插入呢?實(shí)際上在編譯為匯編語(yǔ)句的時(shí)候秽誊,當(dāng)JVM發(fā)現(xiàn)有變量被volatile修飾時(shí)鲸沮,會(huì)加入LOCK前綴指令,這個(gè)指令的作用就是增加內(nèi)存屏障锅论。這就是為什么有了MESI協(xié)議讼溺,我們還需要volatile的原因之一。因?yàn)関olatile變量讀寫(xiě)前后插入內(nèi)存屏障指令最易,所以cache之間的數(shù)據(jù)能夠及時(shí)感知到怒坯,共享變量就達(dá)到了線(xiàn)程間可見(jiàn)性,也就是說(shuō)volatile修飾的變量具有線(xiàn)程間可見(jiàn)藻懒。

Unsafe

常用的功能:

  • 對(duì)象操作
  • CAS
  • 線(xiàn)程掛起/喚醒
對(duì)象操作

Java 雖然屏蔽了指針剔猿,但是底層還是通過(guò)指針訪(fǎng)問(wèn)的。因此嬉荆,只要獲取了對(duì)象在內(nèi)存中的地址归敬,找到其中字段在對(duì)象里的偏移,就可以訪(fǎng)問(wèn)相應(yīng)的字段鄙早。

class Student {
        int age;
        char name;

        private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
        private static final long AGE;

        static {
            try {
                //AGE 為age變量在Student對(duì)象里的偏移量
                AGE = U.objectFieldOffset
                        (Student.class.getDeclaredField("age"));
            } catch (ReflectiveOperationException e) {
                throw new Error(e);
            }
        }

        //改變age的值
        private void setAge(int age) {
            U.putInt(this, AGE, age);
        }
    }

1弄慰、通過(guò)Unsafe獲取age字段在Student對(duì)象里的偏移量

2、通過(guò)對(duì)象基準(zhǔn)地址+偏移量就可以定位到age字段蝶锋,進(jìn)而可以訪(fǎng)問(wèn)(讀/寫(xiě))

3陆爽、此處是拿到偏移量后通過(guò)Unsafe修改

CAS

CAS 操作包含三個(gè)操作數(shù) —— 內(nèi)存位置(V)、預(yù)期原值(A)和新值(B)扳缕。 如果內(nèi)存位置的值與預(yù)期原值相匹配慌闭,那么處理器會(huì)自動(dòng)將該位置值更新為新值 。否則躯舔,處理器不做任何操作驴剔。

CAS雖然很高效的解決原子操作,但是CAS仍然存在三大問(wèn)題粥庄。ABA問(wèn)題丧失,循環(huán)時(shí)間長(zhǎng)開(kāi)銷(xiāo)大和只能保證一個(gè)共享變量的原子操作

  1. ABA問(wèn)題。因?yàn)镃AS需要在操作值的時(shí)候檢查下值有沒(méi)有發(fā)生變化惜互,如果沒(méi)有發(fā)生變化則更新布讹,但是如果一個(gè)值原來(lái)是A,變成了B训堆,又變成了A描验,那么使用CAS進(jìn)行檢查時(shí)會(huì)發(fā)現(xiàn)它的值沒(méi)有發(fā)生變化,但是實(shí)際上卻變化了坑鱼。ABA問(wèn)題的解決思路就是使用版本號(hào)肖方。在變量前面追加上版本號(hào),每次變量更新的時(shí)候把版本號(hào)加一负饲,那么A-B-A 就會(huì)變成1A-2B-3A。

從Java1.5開(kāi)始JDK的atomic包里提供了一個(gè)類(lèi)AtomicStampedReference來(lái)解決ABA問(wèn)題耕魄。這個(gè)類(lèi)的compareAndSet方法作用是首先檢查當(dāng)前引用是否等于預(yù)期引用,并且當(dāng)前標(biāo)志是否等于預(yù)期標(biāo)志彭谁,如果全部相等屎开,則以原子方式將該引用和該標(biāo)志的值設(shè)置為給定的更新值。

  1. 循環(huán)時(shí)間長(zhǎng)開(kāi)銷(xiāo)大马靠。自旋CAS如果長(zhǎng)時(shí)間不成功,會(huì)給CPU帶來(lái)非常大的執(zhí)行開(kāi)銷(xiāo)蔼两。如果JVM能支持處理器提供的pause指令那么效率會(huì)有一定的提升甩鳄,pause指令有兩個(gè)作用,第一它可以延遲流水線(xiàn)執(zhí)行指令(de-pipeline),使CPU不會(huì)消耗過(guò)多的執(zhí)行資源额划,延遲的時(shí)間取決于具體實(shí)現(xiàn)的版本妙啃,在一些處理器上延遲時(shí)間是零。第二它可以避免在退出循環(huán)的時(shí)候因內(nèi)存順序沖突(memory order violation)而引起CPU流水線(xiàn)被清空(CPU pipeline flush)俊戳,從而提高CPU的執(zhí)行效率揖赴。

  2. 只能保證一個(gè)共享變量的原子操作。當(dāng)對(duì)一個(gè)共享變量執(zhí)行操作時(shí)抑胎,我們可以使用循環(huán)CAS的方式來(lái)保證原子操作燥滑,但是對(duì)多個(gè)共享變量操作時(shí),循環(huán)CAS就無(wú)法保證操作的原子性阿逃,這個(gè)時(shí)候就可以用鎖铭拧,或者有一個(gè)取巧的辦法,就是把多個(gè)共享變量合并成一個(gè)共享變量來(lái)操作恃锉。比如有兩個(gè)共享變量i=2,j=a搀菩,合并一下ij=2a,然后用CAS來(lái)操作ij破托。從Java1.5開(kāi)始JDK提供了AtomicReference類(lèi)來(lái)保證引用對(duì)象之間的原子性肪跋,你可以把多個(gè)變量放在一個(gè)對(duì)象里來(lái)進(jìn)行CAS操作。

鎖的本質(zhì)

需要理解線(xiàn)程互斥和線(xiàn)程同步的區(qū)別

線(xiàn)程互斥只需要一個(gè)阻塞隊(duì)列土砂,利用CAS去獲取鎖州既,獲取鎖成功的進(jìn)入臨界區(qū),而沒(méi)有成功的線(xiàn)程則插入阻塞隊(duì)列的隊(duì)尾萝映,當(dāng)釋放鎖的時(shí)候易桃,則從隊(duì)列的頭部取出喚醒,繼續(xù)競(jìng)爭(zhēng)鎖锌俱。

而線(xiàn)程同步則多了一個(gè)同步隊(duì)列晤郑。

當(dāng)只有一個(gè)變量的時(shí)候,我們可以采用CAS可以實(shí)現(xiàn)互斥的訪(fǎng)問(wèn)共享變量。

static AtomicInteger a = new AtomicInteger(0);
    private static void inc() {
        a.incrementAndGet();
    }

但是當(dāng)多個(gè)變量的時(shí)候造寝,而且每個(gè)變量都需要互斥怎么處理磕洪,這個(gè)時(shí)候我們把多個(gè)變量看做一個(gè)臨界區(qū),當(dāng)多個(gè)線(xiàn)程操作的時(shí)候诫龙,只有獲取到鎖的才能進(jìn)入臨界區(qū)操作析显。其它沒(méi)拿到"鎖"的線(xiàn)程一直嘗試拿"鎖",當(dāng)擁有鎖的線(xiàn)程退出臨界區(qū)后釋放"鎖"签赃,其它就可以拿到"鎖"了谷异。

但是這又有問(wèn)題,不只是兩個(gè)線(xiàn)程競(jìng)爭(zhēng)鎖锦聊,而是很多線(xiàn)程同時(shí)競(jìng)爭(zhēng)鎖歹嘹。臨界區(qū)執(zhí)行時(shí)間很長(zhǎng),鎖很難被釋放出來(lái)孔庭。那么沒(méi)獲取到鎖的線(xiàn)程一直無(wú)限循環(huán)去嘗試獲取尺上,如此一來(lái)很浪費(fèi)CPU。在這種情況下圆到,我們采取線(xiàn)程掛起/喚醒策略怎抛。將掛起的線(xiàn)程放入隊(duì)列里,當(dāng)另一個(gè)線(xiàn)程釋放鎖后從隊(duì)列里取出當(dāng)初被掛起的線(xiàn)程并喚醒它芽淡,被喚醒的線(xiàn)程繼續(xù)去競(jìng)爭(zhēng)鎖马绝。

前面講的可以理解為多個(gè)線(xiàn)程互斥訪(fǎng)問(wèn)臨界區(qū),這些線(xiàn)程對(duì)臨界區(qū)的操作僅僅是互斥挣菲,并沒(méi)有其它依賴(lài)關(guān)系迹淌。

1、當(dāng)需要等待的時(shí)候己单,走紅色線(xiàn)條部分唉窃。將線(xiàn)程放入到等待隊(duì)列里、釋放鎖纹笼,并喚醒阻塞隊(duì)列里的線(xiàn)程纹份。

2、當(dāng)需要通知的時(shí)候廷痘,走綠色線(xiàn)條部分蔓涧,將線(xiàn)程從等待隊(duì)列里移除,并將之加入到阻塞隊(duì)列里笋额。

3元暴、線(xiàn)程同步的過(guò)程比線(xiàn)程互斥過(guò)程新增了等待隊(duì)列,該隊(duì)列存儲(chǔ)著因某種條件而掛起等待的線(xiàn)程兄猩。當(dāng)另一個(gè)線(xiàn)程發(fā)現(xiàn)條件滿(mǎn)足后茉盏,通知等待隊(duì)列里的線(xiàn)程鉴未,讓它繼續(xù)做事。

所以可以簡(jiǎn)單的理解為鸠姨,鎖是CAS + 阻塞隊(duì)列+等待隊(duì)列一起合作實(shí)現(xiàn)線(xiàn)程同步铜秆。

Synchronized
synchronized 各種使用方式

1、無(wú)論是修飾方法還是代碼塊讶迁,最終都是獲取對(duì)象鎖(類(lèi)鎖是Class對(duì)象的鎖)

2连茧、實(shí)例方法與對(duì)象鎖獲取的是同一把鎖(普通對(duì)象鎖)

3、靜態(tài)方法與類(lèi)鎖獲取的是同一把鎖(類(lèi)鎖-Class對(duì)象鎖)

synchronized

monitorenter 表示獲取鎖

monitorexit 表示釋放鎖

兩者之間的操作就是被鎖住的臨界區(qū)

其中monitorexit 有兩個(gè)巍糯,后面一個(gè)是發(fā)生異常時(shí)會(huì)執(zhí)行

修飾方法和代碼塊的區(qū)別:

1啸驯、修飾代碼塊時(shí)編譯后會(huì)在臨界區(qū)前后加入monitorenter、monitorexit 指

2祟峦、修飾方法時(shí)進(jìn)入/退出方法時(shí)會(huì)判斷ACC_SYNCHRONIZED 標(biāo)記是否存在

3罚斗、不管是用monitorenter/monitorexit 還是ACC_SYNCHRONIZED,最終都是在對(duì)象頭上做文章搀愧,都需要獲取鎖。

對(duì)象頭

new 出來(lái)的對(duì)象放在堆里疆偿,而對(duì)象在堆里的結(jié)構(gòu)為:對(duì)象頭咱筛、實(shí)例數(shù)據(jù)(age/name)、填充字節(jié)杆故。

對(duì)象頭可以分為:MarK Word,Klass Word,和數(shù)組長(zhǎng)度迅箩,其中數(shù)組長(zhǎng)度只有數(shù)組對(duì)象才會(huì)有數(shù)組長(zhǎng)度部分。

Mark Word有五種狀態(tài)处铛,biased_lock占1bit饲趋,來(lái)控制偏向鎖與非偏向鎖,lock 占2bits撤蟆,來(lái)控制四種狀態(tài)奕塑,

代表狀態(tài) lock
無(wú)鎖 01
偏向鎖 01
輕量級(jí)鎖 00
重量級(jí)鎖 10
GC標(biāo)記 11

輕量級(jí)鎖:線(xiàn)程間交替執(zhí)行臨界區(qū)的情形下使用的鎖

偏向鎖:當(dāng)鎖偏向某個(gè)線(xiàn)程時(shí),該線(xiàn)程再次獲取鎖無(wú)需CAS

重量級(jí)鎖:一個(gè)線(xiàn)程沒(méi)有獲取到鎖然后掛起自己家肯,等其他的線(xiàn)程釋放鎖后喚醒自己龄砰,而線(xiàn)程的掛起、喚醒需要CPU切換上下文讨衣,此過(guò)程代價(jià)比較大换棚,稱(chēng)為重量級(jí)鎖。

輕量級(jí)鎖與重量級(jí)鎖的區(qū)別:

1反镇、每次加鎖只需要一次CAS

2固蚤、不需要分配ObjectMonitor對(duì)象

3、線(xiàn)程無(wú)需掛起與喚醒

偏向鎖相比輕量級(jí)鎖的優(yōu)勢(shì):

同一個(gè)線(xiàn)程多次獲取鎖時(shí)歹茶,無(wú)需再次進(jìn)行CAS夕玩,只需要簡(jiǎn)單比較你弦。

public class TestDemo {
    public static void main(String args[]) {
        Object object = new Object();
        //打印虛擬機(jī)的信息
        System.out.println(VM.current().details());
        //打印對(duì)象大小
        System.out.println(ClassLayout.parseInstance(object).instanceSize());
        //打印對(duì)象頭大小
        System.out.println(ClassLayout.parseInstance(object).headerSize());
        //打印對(duì)象信息
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }
}

這種情況下為無(wú)鎖狀態(tài)。

public class TestDemo {
    public static void main(String args[]) {
        Object object = new Object();
        //打印對(duì)象信息
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
        synchronized (object) {
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }
}

上鎖前后都是無(wú)鎖狀態(tài)风秤,上了鎖后是輕量級(jí)鎖鳖目。

JVM 啟動(dòng)的時(shí)候沒(méi)有立即開(kāi)啟偏向鎖,而是延遲開(kāi)啟缤弦。延遲時(shí)間是4s

public class TestDemo {
    public static void main(String args[]) {
        try {
            Thread.sleep(4500);
        } catch (Exception e) {

        }
        Object object = new Object();
        //打印對(duì)象信息
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
        synchronized (object) {
            System.out.println(ClassLayout.parseInstance(object).toPrintable());
        }
        System.out.println(ClassLayout.parseInstance(object).toPrintable());
    }
}

偏向鎖一旦開(kāi)啟了领迈,默認(rèn)就是偏向鎖。退出臨界區(qū)后碍沐,還是偏向鎖狸捅。

public class TestDemo {
    static Object object = new Object();
    public static void main(String args[]) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("before get lock in Thread1");
                System.out.println(ClassLayout.parseInstance(object).toPrintable());
                synchronized (object) {
                    System.out.println("after get lock in Thread1");
                    System.out.println(ClassLayout.parseInstance(object).toPrintable());
                    new Thread(new Runnable() {
                        @Override
                        public void run() {
                            System.out.println("before get lock in Thread2");
                            System.out.println(ClassLayout.parseInstance(object).toPrintable());
                            synchronized (object) {
                                System.out.println("after get lock in Thread2");
                                System.out.println(ClassLayout.parseInstance(object).toPrintable());
                            }

                        }
                    }, "t2").start();

                    sleep(5000);
                }
            }
        }, "t1").start();
    }
}

t1未獲取鎖之前為無(wú)鎖,t1獲取鎖之后為輕量級(jí)鎖累提,t2獲取鎖之前為輕量級(jí)鎖尘喝,t2獲取鎖之后則為重量級(jí)鎖。

偏向鎖的加鎖過(guò)程

偏向鎖里存儲(chǔ)了偏向線(xiàn)程的id斋陪,epoch朽褪,偏向鎖標(biāo)記(biased_lock),鎖標(biāo)記(lock)等信息无虚。這些信息統(tǒng)稱(chēng)為Mark Word缔赠。

1、先判斷Mark Word里的線(xiàn)程id是否有值友题。

1.1嗤堰、如果沒(méi)有,說(shuō)明還沒(méi)有線(xiàn)程占用鎖度宦,則直接將t1的線(xiàn)程id記錄到Mark Word里踢匣。可能會(huì)存在多個(gè)線(xiàn)程同時(shí)修改Mark Word戈抄,因此需要進(jìn)行CAS修改Mark Word离唬。

1.2、如果已有id值划鸽,那么判斷分兩種情況:

1.2.1男娄、該id是t1的id,則此次獲取鎖是個(gè)重入的過(guò)程漾稀,直接就獲取了模闲。

1.2.2、如果該id不是t1的id崭捍,說(shuō)明已經(jīng)有其它線(xiàn)程獲取了鎖尸折,t1想要獲取鎖就需要走撤銷(xiāo)流程。

重量級(jí)鎖的原理

當(dāng)鎖處在輕量級(jí)鎖的狀態(tài)時(shí)殷蛇,Mark Word 存放著指向Lock Record指針实夹,Lock Record是線(xiàn)程私有的橄浓。Mark Word 存放著指向ObjectMonitor的指針,ObjectMonitor是線(xiàn)程間共享的并且擁有比Lock Record更多的信息亮航。

鎖的膨脹過(guò)程

創(chuàng)建荸实、獲取ObjectMonitor對(duì)象的過(guò)程既是鎖膨脹過(guò)程。

  1. 判斷是不是重量鎖缴淋,是的話(huà)直接返回准给,不是繼續(xù)判斷是否正在膨脹。
  2. 正在膨脹則等待膨脹完重抖,continue,如果不是正在膨脹露氮,則判斷是不是輕量級(jí)鎖。
  3. 如果是輕量級(jí)鎖钟沛,通過(guò)CAS將Mark word指向ObjectMonitor畔规,并將_owner指向Lock Record,然后返回恨统,如果不是輕量級(jí)鎖叁扫,即無(wú)所狀態(tài),通過(guò)CAS將Mark word指向ObjectMonitor畜埋,并將_owner置空莫绣。然后返回重量級(jí)鎖對(duì)象。
加鎖過(guò)程

先CAS嘗試修改ObjectMonitor的_owner字段由捎,會(huì)有幾種結(jié)果:
1兔综、鎖沒(méi)被其它線(xiàn)程占用饿凛,當(dāng)前線(xiàn)程成功獲取鎖狞玛。

2、鎖被當(dāng)前線(xiàn)程占用涧窒,當(dāng)前線(xiàn)程重入該鎖心肪,獲取鎖成功。

3纠吴、鎖被LockRecord占用硬鞍,而LockRecord又屬于當(dāng)前線(xiàn)程,屬于重入戴已,重入次數(shù)為1固该。

4、以上條件都不滿(mǎn)足糖儡,調(diào)用EnterI()函數(shù)伐坏。

5、多次嘗試加鎖握联。(再次加鎖失敗后桦沉,嘗試10次自旋加鎖)

6每瞒、自旋加鎖還是失敗,將線(xiàn)程包裝后加入到阻塞隊(duì)列里纯露。

7剿骨、再?lài)L試獲取鎖

8埠褪、失敗后將自己掛起浓利。

9、被喚醒后繼續(xù)嘗試獲取鎖
组橄。
10荞膘、成功則退出流程,失敗繼續(xù)走上面的流程

釋放鎖的過(guò)程
  1. 先判斷是否有重入玉工,有重入羽资,直接釋放鎖。
  2. 沒(méi)有重入遵班,先釋放鎖屠升,然后判斷——cxq與EntryList是否有線(xiàn)程在等待唄喚醒。沒(méi)有的話(huà)則直接釋放鎖完成
  3. 有則再次獲取鎖狭郑,失敗則吃方所完成腹暖,
  4. 成功則從EntryList的頭節(jié)點(diǎn)取,取出成功則釋放鎖翰萨,并且喚醒線(xiàn)程脏答。
  5. 失敗則從cxq取頭節(jié)點(diǎn),并將EntryList指向_cxq的頭亩鬼。

1殖告、加鎖過(guò)程是不斷地嘗試加鎖,實(shí)在不行了才放入隊(duì)列里,而且還是插入隊(duì)列頭的位置,最后才掛起自己谣沸。

2、想象一種場(chǎng)景:現(xiàn)在A(yíng)線(xiàn)程持有鎖爽丹,B線(xiàn)程在隊(duì)列里等待,在A(yíng)釋放鎖的時(shí)候辛蚊,C線(xiàn)程剛好插進(jìn)來(lái)獲取鎖粤蝎,還未等B被A喚醒,C就獲取了鎖袋马,B苦苦等待那么久還是沒(méi)有獲取鎖初澎。B線(xiàn)程不排隊(duì)的行為造成了不公平競(jìng)爭(zhēng)鎖。

3飞蛹、再想象另一種場(chǎng)景:還是A線(xiàn)程持有鎖谤狡,B線(xiàn)程在隊(duì)列里等待灸眼,此時(shí)C線(xiàn)程也要獲取鎖,因此要進(jìn)入隊(duì)列里排隊(duì)墓懂,此處進(jìn)入的是隊(duì)列頭焰宣,也就是在B的前面排著。當(dāng)A釋放鎖后捕仔,喚醒隊(duì)列里的頭節(jié)點(diǎn)匕积,也就是C線(xiàn)程。C線(xiàn)程插隊(duì)的行為造成了不公平競(jìng)爭(zhēng)鎖榜跌。

4闪唆、綜合1、2钓葫、3點(diǎn)可知悄蕾,因?yàn)橛凶吆箝T(mén)(不排隊(duì))\、插隊(duì)(插到隊(duì)頭)础浮、重量級(jí)鎖是不公平鎖帆调。

總結(jié)圖
微信圖片_20210917174119.jpg
AQS

第一、得需要共享變量作為"鎖芯"豆同。由于這個(gè)共享變量是多線(xiàn)程共享番刊,為保證線(xiàn)程間的可見(jiàn)性,因此需要用volatile關(guān)鍵字修飾影锈。

第二芹务、當(dāng)線(xiàn)程競(jìng)爭(zhēng)鎖成功時(shí)則進(jìn)入臨界區(qū)執(zhí)行代碼,當(dāng)失敗時(shí)需要加入到隊(duì)列里進(jìn)行等待鸭廷,因此需要一個(gè)同步隊(duì)列枣抱,用以存放因獲取鎖失敗而掛起的線(xiàn)程。

第三靴姿、線(xiàn)程之間需要同步沃但,A線(xiàn)程等待B線(xiàn)程生產(chǎn)數(shù)據(jù)磁滚,B線(xiàn)程生產(chǎn)了數(shù)據(jù)通知A線(xiàn)程佛吓,因此需要一個(gè)等待(條件)隊(duì)列。

數(shù)據(jù)結(jié)構(gòu)準(zhǔn)備好之后垂攘,需要操作以上數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn)鎖功能维雇。

線(xiàn)程競(jìng)爭(zhēng)鎖:通過(guò)CAS操作"鎖芯",操作成功則執(zhí)行臨界區(qū)代碼晒他,失敗則加入到同步隊(duì)列吱型。

線(xiàn)程被喚醒:拿到鎖的線(xiàn)程執(zhí)行完臨界區(qū)代碼后釋放鎖,并喚醒同步隊(duì)列里等待的線(xiàn)程陨仅,被喚醒的線(xiàn)程繼續(xù)競(jìng)爭(zhēng)鎖津滞。

線(xiàn)程等待某個(gè)條件:線(xiàn)程因?yàn)槟撤N條件不滿(mǎn)足于是加入到等待隊(duì)列里铝侵,釋放鎖,并掛起等待触徐。 (使用condition才會(huì)初始化這個(gè)隊(duì)列)

條件滿(mǎn)足線(xiàn)程被喚醒:條件滿(mǎn)足咪鲜,線(xiàn)程被喚醒后繼續(xù)競(jìng)爭(zhēng)鎖。

獨(dú)占模式(EXCLUSIVE)

如圖所示撞鹉,有1疟丙,2,3三個(gè)線(xiàn)程鸟雏,假設(shè)1先拿到鎖享郊,此時(shí)將state設(shè)置為1,并將當(dāng)前線(xiàn)程的名字設(shè)置進(jìn)去。然后2進(jìn)來(lái)爭(zhēng)用鎖悼泌,相當(dāng)于acquire操作复斥,調(diào)用getState被去,發(fā)現(xiàn)state的值為1,表示鎖已經(jīng)被別人拿了,所以則會(huì)初始化一個(gè)同步隊(duì)列累贤,初始化一個(gè)頭結(jié)點(diǎn),然后將線(xiàn)程2封裝成一個(gè)Node節(jié)點(diǎn)卖氨,放在頭結(jié)點(diǎn)的后面会烙,并將頭結(jié)點(diǎn)的waitStatus變成SIGNAL,將線(xiàn)程2放入隊(duì)列的時(shí)候有兩步操作筒捺,1柏腻,入隊(duì)列,2申請(qǐng)入隊(duì)系吭。而在申請(qǐng)入隊(duì)的時(shí)候會(huì)有一次爭(zhēng)用鎖的機(jī)會(huì)五嫂,如果在這個(gè)時(shí)候拿到鎖,就不需要入隊(duì)列肯尺。而將waitStatus變成SIGNAL表示頭結(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)是需要喚醒的Node沃缘。

release操作:

釋放鎖的時(shí)候,則將2喚醒蟆盹,需要下面三個(gè)步驟:

1孩灯、檢查當(dāng)前線(xiàn)程是否和持有鎖的線(xiàn)程是同一個(gè)線(xiàn)程

2闺金、修改當(dāng)前鎖的狀態(tài)逾滥,置為0

3、喚醒隊(duì)列中頭結(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)的線(xiàn)程

喚醒動(dòng)作:

1、將自己的waitStatus設(shè)置成0

2寨昙、真正的喚醒操作讥巡,調(diào)用LockSupport.unpark(s.thread)
共享模式

acquire操作

1、共享模式可以被多個(gè)線(xiàn)程同時(shí)拿到舔哪,具體同步器的實(shí)現(xiàn)類(lèi)欢顷,是否允許,看每個(gè)同步器自己的實(shí)現(xiàn)捉蚤。

2抬驴、節(jié)點(diǎn)的waitStatus設(shè)置成 SHARED

3、只修改state字段缆巧,不設(shè)置持有鎖的線(xiàn)程(區(qū)別)

release操作:

1布持、修改當(dāng)前鎖的狀態(tài),置為0

2陕悬、喚醒隊(duì)列中頭結(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn)的線(xiàn)程

喚醒動(dòng)作:

1题暖、將自己的waitStatus設(shè)置成0

2、真正的喚醒操作捉超,調(diào)用LockSupport.unpark(s.thread)

3胧卤、取當(dāng)前被喚醒的節(jié)點(diǎn)的一個(gè)節(jié)點(diǎn),如果下一個(gè)節(jié)點(diǎn)也是SHARED模式拼岳,則直接喚醒枝誊。


微信圖片_20220215103500.jpg
Condition

在前面的基礎(chǔ)上多加了一個(gè)條件隊(duì)列。調(diào)用await的時(shí)候惜纸,創(chuàng)建條件節(jié)點(diǎn)侧啼,必要的時(shí)候初始化條件隊(duì)列或者吧自己加入條件隊(duì)列中,釋放當(dāng)前線(xiàn)程所占有的鎖堪簿,執(zhí)行AQS的release操作痊乾,然后掛起當(dāng)前線(xiàn)程。調(diào)用single操作的時(shí)候椭更,將條件隊(duì)列中的節(jié)點(diǎn)(firstWaiter)轉(zhuǎn)移到同步隊(duì)列中哪审。將加入同步隊(duì)列中的條件節(jié)點(diǎn)的waitStatus變成SIGNAL,因?yàn)樵谕疥?duì)列的waitStatus為CONDITION虑瀑。

其他的跟前面的操作一模一樣湿滓。

微信圖片_20220215103512.jpg
v2-02b2de58d34b5f021f3981eabe3b8638_r.jpg
v2-3c14a01499d9af34eeda4ec392d36065_r.jpg

關(guān)于ReentrantLock的源碼分析請(qǐng)參考:

AQS源碼分析

原子類(lèi)
 /**
     * LongAdder
     * LongAdder克服了高并發(fā)下使用AtomicLong的缺點(diǎn)。既然AtomicLong的性能瓶頸是由于過(guò)多線(xiàn)程同時(shí)去競(jìng)爭(zhēng)一個(gè)變量的更新而產(chǎn)生的舌狗,LongAdder則是把一個(gè)變量分解為多個(gè)變量叽奥,讓同樣多的線(xiàn)程去競(jìng)爭(zhēng)多個(gè)資源,解決了性能問(wèn)題痛侍。
     * LongAdder 是把一個(gè)變量拆成多份朝氓,變?yōu)槎鄠€(gè)變量,有點(diǎn)像 ConcurrentHashMap 中 的分段鎖把一個(gè)Long型拆成一個(gè)base變量外加多個(gè)Cell,每個(gè)Cell包裝了一個(gè)Long型變量赵哲。
     * 這樣待德,在同等并發(fā)量的情況下,爭(zhēng)奪單個(gè)變量更新操作的線(xiàn)程量會(huì)減少枫夺,這變相地減少了爭(zhēng)奪共享資源的并發(fā)量将宪。<br />另外,多個(gè)線(xiàn)程在爭(zhēng)奪同一個(gè)Cell原子變量時(shí)如果失敗了橡庞,
     * 它并不是在當(dāng)前Cell變量上一直自旋CAS重試较坛,而是嘗試在其他Cell的變量上進(jìn)行CAS嘗試,這個(gè)改變?cè)黾恿水?dāng)前線(xiàn)程重試CAS成功的可能性扒最。
     * 最后燎潮,在獲取LongAdder當(dāng)前值時(shí),是把所有Cell變量的value值累加后再加上base返回的扼倘。LongAdder維護(hù)了一個(gè)延遲初始化的原子性更新數(shù)組(默認(rèn)情況下Cell數(shù)組是null)和一個(gè)基值變量base确封。
     * 由于Cells占用的內(nèi)存是相對(duì)比較大的,所以一開(kāi)始并不創(chuàng)建它再菊,而是在需要時(shí)創(chuàng)建爪喘,也就是惰性加載。
     */
    @RequiresApi(api = Build.VERSION_CODES.N)
    private void AtomicExample9() {
        AtomicLongTest();
        LongAdderTest();
    }

    @RequiresApi(api = Build.VERSION_CODES.N)
    private void LongAdderTest() {
        int requestTotal = 100;
        final LongAdder count = new LongAdder();
        final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
        long start = System.currentTimeMillis();
        for (int i = 0; i < requestTotal; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        TimeUnit.MILLISECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    add(count);
                    countDownLatch.countDown();
                }
            }).start();
            Log.e("zzf","count=" + count);
            Log.e("zzf","耗時(shí):" + (System.currentTimeMillis() - start));
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.N)
    private void add(LongAdder count) {
        count.add(1);
    }

    private void AtomicLongTest() {
        // 并發(fā)線(xiàn)程數(shù)
        int requestTotal = 500;
        // 求和總數(shù)
        final int sumTotal = 1000000;

        final AtomicLong count = new AtomicLong(0);
        ExecutorService executorService = Executors.newFixedThreadPool(requestTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
        long start = System.currentTimeMillis();
        for (int i = 0; i < requestTotal; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    add2(sumTotal,count);
                    countDownLatch.countDown();
                }
            });
        }
        Log.e("zzf","count=" + count);
        Log.e("zzf","耗時(shí):" + (System.currentTimeMillis() - start));
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
    }

    private void add2(int sumTotal,AtomicLong count) {
        for (int j = 0; j < sumTotal; j++) {
            count.getAndIncrement();
        }
    }

    /**
     * AtomicStampedReference
     * 原子更新帶有版本號(hào)的引用類(lèi)型纠拔,該類(lèi)將整數(shù)值與引用關(guān)聯(lián)起來(lái)秉剑,可用于原子的更新數(shù)據(jù)和數(shù)據(jù)版本號(hào),可以解決使用CAS進(jìn)行原子更新時(shí)可能出現(xiàn)的ABA問(wèn)題稠诲。
     */
    private void AtomicExample8() {
        /**
         * 1表示版本號(hào)
         */
        final AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(99, 1);
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = atomicStampedReference.getStamp();
                Log.e("zzf",Thread.currentThread().getName() + "---首次 stamp: " + stamp);
                atomicStampedReference.compareAndSet(99, 100,
                        atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
                Log.e("zzf",Thread.currentThread().getName() + "---第二次 stamp: " + atomicStampedReference.getStamp());
                atomicStampedReference.compareAndSet(100, 99,
                        atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
                Log.e("zzf",Thread.currentThread().getName() + "---第三次 stamp: " + atomicStampedReference.getStamp());
            }
        },"t1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = atomicStampedReference.getStamp();
                Log.e("zzf",Thread.currentThread().getName() + "---首次 stamp: " + stamp);
                try {
                    Log.e("zzf",Thread.currentThread().getName() + "---你的校園網(wǎng)正在嘗試重新連接......");
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean result = atomicStampedReference.compareAndSet(99, 100,
                        stamp, stamp + 1);
                Log.e("zzf",Thread.currentThread().getName() + "---修改成功與否:" + result + "  當(dāng)前 stamp:" + atomicStampedReference.getStamp());
                Log.e("zzf",Thread.currentThread().getName() + "---當(dāng)前課程已選人數(shù):" + atomicStampedReference.getReference());
            }
        },"t2");
        t1.start();
        t2.start();
    }

    /**
     * AtomicMarkableReference 通過(guò) boolean 值作為是否更改的標(biāo)記侦鹏,所以他的版本號(hào)只有 true 和false。在并發(fā)中如果兩個(gè)版本號(hào)不斷地切換臀叙,
     * 任然不能很好地解決 ABA 問(wèn)題略水,只是從某種程度降低了ABA事件發(fā)生。
     * AtomicMarkableReference只有true和false兩種狀態(tài)劝萤,如果來(lái)回切換渊涝,也不能很好的解決ABA問(wèn)題。所以引入AtomicStampedReference床嫌。
     */
    private void AtomicExample7() {
        Teacher teacher = new Teacher( "小春哥",200);
        AtomicMarkableReference<Teacher> markableReference = new AtomicMarkableReference<>(teacher, true);
        Teacher newTeacher = new Teacher("懵懂少年",210);
        Log.e("zzf","當(dāng)前返回狀態(tài): "+markableReference.compareAndSet(teacher, newTeacher, true, false));
        Log.e("zzf","AtomicMarkableReference 狀態(tài): "+markableReference.isMarked());
        Teacher twoTeacher = new Teacher("懵懂少年",201);
        Log.e("zzf","當(dāng)前返回狀態(tài): "+markableReference.compareAndSet(teacher, newTeacher, true, false));
        Log.e("zzf","AtomicMarkableReference 狀態(tài): "+markableReference.isMarked());
    }

    /**
     * 允許原子更新指定類(lèi)的指定voltile字段
     * 更新原子對(duì)象的對(duì)象有兩個(gè)條件
     * 1:因?yàn)樵痈伦侄晤?lèi)都是抽象類(lèi)跨释,每次使用的時(shí)候必須使用靜態(tài)方法newUpdater()創(chuàng)建一個(gè)更新器,并且需要設(shè)置想要更新的類(lèi)和屬性厌处。
     * newUpdater(Teacher.class, String.class, "name"):表示想要更新Teacher類(lèi)中name字段鳖谈,name字段是String類(lèi)型,所以傳入String.class
     */
    private void AtomicExample6() {
        AtomicReferenceFieldUpdater referenceFieldUpdater = AtomicReferenceFieldUpdater.newUpdater(Teacher.class, String.class, "name");
        Teacher teacher = new Teacher("小春哥", 200);
        referenceFieldUpdater.compareAndSet(teacher, "小春哥", "公眾號(hào):山間木匠");
        Log.e("zzf",teacher.getName() + "----------" + teacher.getTicketNum());
    }

    /**
     * 原子更新引用類(lèi)型,更新一個(gè)對(duì)象
     */
    @RequiresApi(api = Build.VERSION_CODES.N)
    private void AtomicExample5() {
        AtomicReference<Teacher> atomicReference = new AtomicReference<>();

        BinaryOperator<Teacher> binaryOperator = new BinaryOperator<Teacher>() {
            @Override
            public Teacher apply(Teacher teacher, Teacher teacher2) {
                return teacher2;
            }
        };

        Teacher teacher = new Teacher("小春哥", 200);
        // 將當(dāng)前對(duì)象設(shè)置到引用對(duì)象 AtomicReference 中
        atomicReference.set(teacher);
        Teacher updateTeacher = new Teacher("懵懂少年", 180);
        // teacher 和 引用類(lèi)型AtomicReference 保存的對(duì)象一致 則能修改成功
        atomicReference.compareAndSet(teacher, updateTeacher);
        Log.e("zzf",atomicReference.get().getName() + "----------" + atomicReference.get().getTicketNum());

        Teacher accumulateTeacher = new Teacher("懵懂少年", 210);
        // 原子性地更新指定對(duì)象阔涉,并且返回AtomicReference更新后的值
        atomicReference.accumulateAndGet(accumulateTeacher, binaryOperator);
        Log.e("zzf",atomicReference.get().getName() + "----------" + atomicReference.get().getTicketNum());
    }

    /**
     * 數(shù)組
     */
    @RequiresApi(api = Build.VERSION_CODES.N)
    private void AtomicExample4() {
        AtomicLongArray arr = new AtomicLongArray(5);
        LongUnaryOperator longUnaryOperator = new LongUnaryOperator() {
            @Override
            public long applyAsLong(long operand) {
                return operand + 10;
            }
        };
        LongBinaryOperator longBinaryOperator = new LongBinaryOperator() {
            @Override
            public long applyAsLong(long left, long right) {
                return left + right;
            }
        };

        /**
         * 自增
         */
        Log.e("zzf","索引 0 incrementAndGet=" + arr.getAndIncrement(0));
        Log.e("zzf","索引 0 incrementAndGet=" + arr.getAndIncrement(0));
        /**
         * 自減
         */
        Log.e("zzf","索引 0 incrementAndGet=" + arr.decrementAndGet(0));
        Log.e("zzf","索引 0 incrementAndGet=" + arr.decrementAndGet(0));
        /**
         * 以原子方式將輸入的數(shù)值與實(shí)例中的值(AtomicLongArray(0)里的value)相加
         */
        Log.e("zzf","索引 0 addAndGet=" + arr.addAndGet(0, 100));

        Log.e("zzf","*********** JDK 1.8 ***********");
        /**
         * 返回新值
         */
        Log.e("zzf","索引 0 getAndUpdate=" + arr.updateAndGet(0, longUnaryOperator));
        /**
         * 返回舊值
         */
        Log.e("zzf","索引 0 getAndUpdate=" + arr.getAndUpdate(0, longUnaryOperator));
        /**
         * 使用給定函數(shù)應(yīng)用給指定下標(biāo)和給定值的結(jié)果原子更新當(dāng)前值,并返回當(dāng)前值
         */
        Log.e("zzf","索引 1 accumulateAndGet=" + arr.accumulateAndGet(1, 10, longBinaryOperator));
    }

    /**
     * 原子
     */
    private void AtomicExample2() {
        int requestTotal = 10;
        final CountDownLatch countDownLatch = new CountDownLatch(requestTotal);
        long start = System.currentTimeMillis();
        for (int i = 0; i < requestTotal; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //add();
                    add1();
                    countDownLatch.countDown();
                }

            }).start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.e("zzf","count=" + count);
        Log.e("zzf","count=" + count1);
        Log.e("zzf","耗時(shí):" + (System.currentTimeMillis() - start));
    }

    private void add() {
        ++count;
    }

    private void add1() {
        count1.getAndIncrement();
    }

    /**
     * AtomicLong通過(guò)CAS提供了非阻塞的原子性操作缆娃,相比使用阻塞算法的同步器來(lái)說(shuō)它的性能已經(jīng)很好了捷绒,但是JDK開(kāi)發(fā)組并不滿(mǎn)足于此。
     * 使用AtomicLong時(shí)龄恋,在高并發(fā)下大量線(xiàn)程會(huì)同時(shí)去競(jìng)爭(zhēng)更新同一個(gè)原子變量疙驾,但是由于同時(shí)只有一個(gè)線(xiàn)程的CAS操作會(huì)成功凶伙,
     * 這就造成了大量線(xiàn)程競(jìng)爭(zhēng)失敗后郭毕,會(huì)通過(guò)無(wú)限循環(huán)不斷進(jìn)行自旋嘗試CAS的操作,而這會(huì)白白浪費(fèi)CPU資源函荣。
     */
    @RequiresApi(api = Build.VERSION_CODES.N)
    private void AtomicExample1() {
        AtomicLong count = new AtomicLong(0);
        LongUnaryOperator longUnaryOperator = new LongUnaryOperator() {
            @Override
            public long applyAsLong(long operand) {
                return 1;
            }
        };

        LongBinaryOperator longBinaryOperator = new LongBinaryOperator() {
            @Override
            public long applyAsLong(long left, long right) {
                return left + right;
            }
        };
        /**
         * getAndIncrement:以原子方式將當(dāng)前值加1显押,返回舊值 (i++)
         */
        Log.e("zzf","getAndIncrement=" + count.getAndIncrement());
        /**
         * 以原子方式將當(dāng)前值加1,返回新值(++i)
         */
        Log.e("zzf","incrementAndGet=" + count.incrementAndGet());
        /**
         * 以原子方式將當(dāng)前值減少 1傻挂,返回舊值(i--)
         */
        Log.e("zzf","incrementAndGet=" + count.getAndDecrement());
        /**
         * /以原子方式將當(dāng)前值減少 1乘碑,返回舊值 (--i)
         */
        Log.e("zzf","incrementAndGet=" + count.decrementAndGet());
        /**
         * 以原子方式將輸入的數(shù)值與實(shí)例中的值(AtomicLong里的value)相加,并返回結(jié)果
         */
        Log.e("zzf","addAndGet=" + count.addAndGet(10));
        /**
         * 以原子方式設(shè)置為`newValue`的值金拒,并返回舊值
         */
        Log.e("zzf","getAndSet=" + count.getAndSet(100));

        Log.e("zzf","get=" + count.get());

        Log.e("zzf","*********** JDK 1.8 ***********");

        /**
         * 使用將給定函數(shù)定函數(shù)的結(jié)果原子更新當(dāng)前值兽肤,返回上一個(gè)值
         */
        Log.e("zzf","getAndUpdate=" + count.getAndUpdate(longUnaryOperator));

        Log.e("zzf","getAndUpdate=" + count.getAndUpdate(longUnaryOperator));
        Log.e("zzf","get=" + count.get());
        /**
         *使用給定函數(shù)應(yīng)用給當(dāng)前值和給定值的結(jié)果原子更新當(dāng)前值,返回上一個(gè)值
         */
        Log.e("zzf","getAndAccumulate=" + count.getAndAccumulate(2, longBinaryOperator));
        Log.e("zzf","getAndAccumulate=" + count.getAndAccumulate(2, longBinaryOperator));
    }
Semaphore/CountDownLatch/CyclicBarrier
/**
     * semaphore
     *
     * 可以用來(lái)控制同時(shí)訪(fǎng)問(wèn)特定資源的線(xiàn)程數(shù)量绪抛,通過(guò)協(xié)調(diào)各個(gè)線(xiàn)程资铡,以保證合理的使用資源。
     * 適用于那些資源有明確訪(fǎng)問(wèn)數(shù)量限制的場(chǎng)景幢码,常用于限流 笤休。
     */
    private void AtomicExample12() {
        final Semaphore semaphore=new Semaphore(10);
        for(int i=0;i<20;i++){
            Thread thread=new Thread(new Runnable() {
                public void run() {
                    try {
                        System.out.println("===="+Thread.currentThread().getName()+"來(lái)到停車(chē)場(chǎng)");
                        if(semaphore.availablePermits()==0){
                            Log.e("zzf","車(chē)位不足,請(qǐng)耐心等待");
                        }
                        semaphore.acquire();//獲取令牌嘗試進(jìn)入停車(chē)場(chǎng)
                        Log.e("zzf",Thread.currentThread().getName()+"成功進(jìn)入停車(chē)場(chǎng)");
                        Thread.sleep(new Random().nextInt(1000));//模擬車(chē)輛在停車(chē)場(chǎng)停留的時(shí)間
                        Log.e("zzf",Thread.currentThread().getName()+"駛出停車(chē)場(chǎng)");
                        semaphore.release();//釋放令牌症副,騰出停車(chē)場(chǎng)車(chē)位
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },i+"號(hào)車(chē)");

            thread.start();
        }
    }

    /**
     * CyclicBarrier與CountDownLatch的區(qū)別
     *
     * CyclicBarrier的計(jì)數(shù)器由自己控制店雅,而CountDownLatch的計(jì)數(shù)器則由使用者來(lái)控制,在CyclicBarrier中線(xiàn)程調(diào)用await方法不僅會(huì)將自己阻塞還會(huì)將計(jì)數(shù)器減1贞铣,
     * 而在CountDownLatch中線(xiàn)程調(diào)用await方法只是將自己阻塞而不會(huì)減少計(jì)數(shù)器的值闹啦。
     *
     * CountDownLatch只能攔截一輪,而CyclicBarrier可以實(shí)現(xiàn)循環(huán)攔截辕坝。
     *
     * CountDownLatch是線(xiàn)程組之間的等待亥揖,即一個(gè)(或多個(gè))線(xiàn)程等待N個(gè)線(xiàn)程完成某件事情之后再執(zhí)行;而CyclicBarrier則是線(xiàn)程組內(nèi)的等待圣勒,
     * 即每個(gè)線(xiàn)程相互等待费变,即N個(gè)線(xiàn)程都被攔截之后,然后依次執(zhí)行圣贸。
     */

    /**
     * CyclicBarrier,CyclicBarrier 基于 Condition 來(lái)實(shí)現(xiàn)的挚歧。
     *
     * 在CyclicBarrier類(lèi)的內(nèi)部有一個(gè)計(jì)數(shù)器,每個(gè)線(xiàn)程在到達(dá)屏障點(diǎn)的時(shí)候都會(huì)調(diào)用await方法將自己阻塞吁峻,此時(shí)計(jì)數(shù)器會(huì)減1滑负,
     * 當(dāng)計(jì)數(shù)器減為0的時(shí)候所有因調(diào)用await方法而被阻塞的線(xiàn)程將被喚醒在张。
     */
    private void AtomicExample11() {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5, new Runnable() {
            @Override
            public void run() {
                Log.e("zzf","都到了");
            }
        });

        Thread_01[] threads = new Thread_01[5];
        for (int i = 0; i < threads.length; i++){
            threads[i] = new Thread_01(cyclicBarrier);
            threads[i].start();
        }
    }

    /**
     * CountDownLatch
     * 類(lèi)似于join,可以使一個(gè)獲多個(gè)線(xiàn)程等待其他線(xiàn)程各自執(zhí)行完畢后再執(zhí)行矮慕。CountDownLatch 定義了一個(gè)計(jì)數(shù)器帮匾,和一個(gè)阻塞隊(duì)列, 當(dāng)計(jì)數(shù)器的值遞減為0之前痴鳄,阻塞隊(duì)列里面的線(xiàn)程處于掛起狀態(tài)瘟斜,
     * 當(dāng)計(jì)數(shù)器遞減到0時(shí)會(huì)喚醒阻塞隊(duì)列所有線(xiàn)程,這里的計(jì)數(shù)器是一個(gè)標(biāo)志痪寻,可以表示一個(gè)任務(wù)一個(gè)線(xiàn)程螺句,也可以表示一個(gè)倒計(jì)時(shí)器,
     * CountDownLatch可以解決那些一個(gè)或者多個(gè)線(xiàn)程在執(zhí)行之前必須依賴(lài)于某些必要的前提業(yè)務(wù)先執(zhí)行的場(chǎng)景橡类。
     *
     * 在下面的例子中蛇尚,主線(xiàn)程會(huì)等待四個(gè)子線(xiàn)程執(zhí)行完再執(zhí)行。
     *
     * CountDownLatch 基于 AQS 的共享模式的使用顾画。
     */
    private void AtomicExample10() {
        //用于聚合所有的統(tǒng)計(jì)指標(biāo)
        final Map map=new HashMap();
        //創(chuàng)建計(jì)數(shù)器取劫,這里需要統(tǒng)計(jì)4個(gè)指標(biāo)
        final CountDownLatch countDownLatch=new CountDownLatch(4);
        //記錄開(kāi)始時(shí)間
        long startTime=System.currentTimeMillis();
        Thread countUserThread=new Thread(new Runnable() {
            public void run() {
                try {
                    System.out.println("正在統(tǒng)計(jì)新增用戶(hù)數(shù)量");
                    Thread.sleep(3000);//任務(wù)執(zhí)行需要3秒
                    map.put("userNumber",1);//保存結(jié)果值
                    countDownLatch.countDown();//標(biāo)記已經(jīng)完成一個(gè)任務(wù)
                    Log.e("zzf","統(tǒng)計(jì)新增用戶(hù)數(shù)量完畢");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread countOrderThread=new Thread(new Runnable() {
            public void run() {
                try {
                    System.out.println("正在統(tǒng)計(jì)訂單數(shù)量");
                    Thread.sleep(3000);//任務(wù)執(zhí)行需要3秒
                    map.put("countOrder",2);//保存結(jié)果值
                    countDownLatch.countDown();//標(biāo)記已經(jīng)完成一個(gè)任務(wù)
                    Log.e("zzf","統(tǒng)計(jì)訂單數(shù)量完畢");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread countGoodsThread=new Thread(new Runnable() {
            public void run() {
                try {
                    System.out.println("正在商品銷(xiāo)量");
                    Thread.sleep(3000);//任務(wù)執(zhí)行需要3秒
                    map.put("countGoods",3);//保存結(jié)果值
                    countDownLatch.countDown();//標(biāo)記已經(jīng)完成一個(gè)任務(wù)
                    Log.e("zzf","統(tǒng)計(jì)商品銷(xiāo)量完畢");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread countmoneyThread=new Thread(new Runnable() {
            public void run() {
                try {
                    System.out.println("正在總銷(xiāo)售額");
                    Thread.sleep(3000);//任務(wù)執(zhí)行需要3秒
                    map.put("countmoney",4);//保存結(jié)果值
                    countDownLatch.countDown();//標(biāo)記已經(jīng)完成一個(gè)任務(wù)
                    Log.e("zzf","統(tǒng)計(jì)銷(xiāo)售額完畢");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //啟動(dòng)子線(xiàn)程執(zhí)行任務(wù)
        countUserThread.start();
        countGoodsThread.start();
        countOrderThread.start();
        countmoneyThread.start();
        try {
            //主線(xiàn)程等待所有統(tǒng)計(jì)指標(biāo)執(zhí)行完畢
            countDownLatch.await();//這個(gè)在哪個(gè)線(xiàn)程,就阻塞哪個(gè)線(xiàn)程研侣,等待其他線(xiàn)程執(zhí)行完再執(zhí)行
            long endTime=System.currentTimeMillis();//記錄結(jié)束時(shí)間
            Log.e("zzf","------統(tǒng)計(jì)指標(biāo)全部完成--------");
            Log.e("zzf","統(tǒng)計(jì)結(jié)果為:"+map.toString());
            Log.e("zzf","任務(wù)總執(zhí)行時(shí)間為"+(endTime-startTime)/1000+"秒");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
參考文章

http://www.reibang.com/p/5334a131151e

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末谱邪,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子义辕,更是在濱河造成了極大的恐慌虾标,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件灌砖,死亡現(xiàn)場(chǎng)離奇詭異璧函,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)基显,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)蘸吓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人撩幽,你說(shuō)我怎么就攤上這事库继。” “怎么了窜醉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵宪萄,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我榨惰,道長(zhǎng)拜英,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮察绷,結(jié)果婚禮上馅笙,老公的妹妹穿的比我還像新娘粮揉。我一直安慰自己,他們只是感情好斟或,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布溉潭。 她就那樣靜靜地躺著倦微,像睡著了一般弄兜。 火紅的嫁衣襯著肌膚如雪药蜻。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,146評(píng)論 1 297
  • 那天挨队,我揣著相機(jī)與錄音谷暮,去河邊找鬼蒿往。 笑死盛垦,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的瓤漏。 我是一名探鬼主播腾夯,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蔬充!你這毒婦竟也來(lái)了蝶俱?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤饥漫,失蹤者是張志新(化名)和其女友劉穎榨呆,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體庸队,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡积蜻,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了彻消。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片竿拆。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖宾尚,靈堂內(nèi)的尸體忽然破棺而出丙笋,到底是詐尸還是另有隱情,我是刑警寧澤煌贴,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布御板,位于F島的核電站,受9級(jí)特大地震影響牛郑,放射性物質(zhì)發(fā)生泄漏怠肋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一井濒、第九天 我趴在偏房一處隱蔽的房頂上張望灶似。 院中可真熱鬧列林,春花似錦、人聲如沸酪惭。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)春感。三九已至砌创,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鲫懒,已是汗流浹背嫩实。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留窥岩,地道東北人甲献。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像颂翼,于是被迫代替她去往敵國(guó)和親晃洒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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