面試復(fù)習(xí)-Java并發(fā)編程

1.Java內(nèi)存模型

Java內(nèi)存模型

JMM的內(nèi)存模型如圖所示,其規(guī)定了所有變量都存儲(chǔ)在主內(nèi)存中,每條線程還有自己的工作內(nèi)存,工作內(nèi)存中保存了被該線程使用到的變量的主內(nèi)存副本拷貝,線程對(duì)變量的所有操作,都必須在工作內(nèi)存中進(jìn)行。
三大特性:

  • 可見(jiàn)性:指當(dāng)一個(gè)線程修改了共享變量的值,其他線程能夠立即得知這個(gè)修改。JMM是通過(guò)在變量修改后將新值同步回主內(nèi)存,在變量讀取前從主內(nèi)存刷新變量值來(lái)實(shí)現(xiàn)可見(jiàn)性的。
  • 原子性:不可分割,某個(gè)線程在做具體某個(gè)事務(wù)時(shí),中間不可以被加塞或分割,需要整體完整。
  • 有序性:在本線程內(nèi)觀察,所有操作都是有序的。在一個(gè)線程觀察另一個(gè)線程,所有操作都是無(wú)序的,無(wú)序是因?yàn)榘l(fā)生了指令重排序。

2.volatitle

volatitle是jvm提供的輕量級(jí)的同步機(jī)制,保證可見(jiàn)性,但不保證完整性,禁止指令重排,其并不是并發(fā)安全的。由于其只保證可見(jiàn)性,在不符合以下兩條規(guī)則的場(chǎng)景中,仍然要通過(guò)加鎖來(lái)保證原子性:

  • 運(yùn)算結(jié)果并不依賴變量的當(dāng)前值,或者能夠確保只有單一的線程修改變量的值。
  • 變量不需要與其他的狀態(tài)變量共同參與不變約束。

可見(jiàn)性保證:volatile保證了修飾的共享變量在轉(zhuǎn)換為匯編語(yǔ)言時(shí),會(huì)加上一個(gè)lock為前綴的指令,當(dāng)CPU發(fā)現(xiàn)這個(gè)指令時(shí),會(huì)立刻做以下兩件事:
1.將當(dāng)前內(nèi)核中線程工作內(nèi)存中該共享變量刷新到主存
2.通知其他內(nèi)核里緩存的該共享變量?jī)?nèi)存地址無(wú)效

3.狀態(tài)切換

Java語(yǔ)言定義了5種線程狀態(tài),在任意一個(gè)時(shí)間點(diǎn),一個(gè)線程只能有且只有一種狀態(tài):

  • 新建(New):創(chuàng)建后尚未啟動(dòng)的線程處于這種狀態(tài)
  • 運(yùn)行(Runnable):處于此狀態(tài)的線程有可能正在執(zhí)行,也有可能正在等待著CPU給它分配時(shí)間
  • 無(wú)限期等待(Waiting):處于這種狀態(tài)的線程不會(huì)被CPU分配時(shí)間,它們要等待被其他線程喚醒??赏ㄟ^(guò)以下方法無(wú)限期等待:
    1.沒(méi)有設(shè)置Timeout參數(shù)的Object.wait()
    2.沒(méi)有設(shè)置Timeout參數(shù)的Thread.join()
    3.LockSupport.park()方法
  • 限期等待(Timed Waiting):這種狀態(tài)下也不會(huì)被分配CPU時(shí)間,不過(guò)無(wú)須等待被喚醒,一段時(shí)間后會(huì)由系統(tǒng)自動(dòng)喚醒。
  • 阻塞(Blocked):線程在等待著獲取到一個(gè)排他鎖。
  • 結(jié)束(Terminated):已終止的線程狀態(tài)。

4.線程安全

當(dāng)多個(gè)線程訪問(wèn)一個(gè)對(duì)象時(shí),如果不用考慮這些線程在運(yùn)行時(shí)環(huán)境下的調(diào)度和交替執(zhí)行,也不需要進(jìn)行額外的同步,或者在調(diào)用方進(jìn)行任何其他的協(xié)調(diào)操作,調(diào)用這個(gè)對(duì)象的行為都可以獲得正確的結(jié)果,那這個(gè)對(duì)象是線程安全的。

5.線程安全的實(shí)現(xiàn)方法

5.1 互斥同步

同步是指多個(gè)線程并發(fā)訪問(wèn)共享數(shù)據(jù)時(shí),保證共享數(shù)據(jù)在同一時(shí)刻只被一個(gè)(或者是一些,使用信號(hào)量的時(shí)候)線程使用。而互斥是實(shí)現(xiàn)同步的一種手段,臨界區(qū)、互斥量和信號(hào)量都是主要的互斥實(shí)現(xiàn)方式。

  • synchronized
    synchronized是JVM實(shí)現(xiàn)的,可用于同步代碼塊,同步一個(gè)方法,同步一個(gè)類,同步一個(gè)靜態(tài)方法。JVM基于進(jìn)入和退出Monitor對(duì)象來(lái)實(shí)現(xiàn)方法同步和代碼塊同步。代碼塊同步是使用monitorenter和monitorexit指令實(shí)現(xiàn)的,而方法同步卻不是。
    Java的線程是映射到操作系統(tǒng)的原生線程智商的,如果要阻塞或喚醒一個(gè)線程,都需要操作系統(tǒng)來(lái)幫忙完成,這就需要從用戶態(tài)切換到內(nèi)核態(tài)種,因此狀態(tài)轉(zhuǎn)換你需要耗費(fèi)很多的處理器時(shí)間,所以synchronized是Java語(yǔ)言的一個(gè)重量級(jí)操作。
  • synchronized的優(yōu)化
  1. 自旋鎖:互斥同步進(jìn)入阻塞狀態(tài)的開(kāi)銷很大。自旋鎖讓一個(gè)線程請(qǐng)求一個(gè)共享數(shù)據(jù)的鎖的時(shí)候執(zhí)行忙循環(huán)一段時(shí)間,如果在這段時(shí)間能獲得鎖,就可以避免進(jìn)入阻塞狀態(tài)。它只適用于共享數(shù)據(jù)鎖定狀態(tài)很短的情況。
  2. 鎖消除:被檢測(cè)出不可能存在競(jìng)爭(zhēng)的共享數(shù)據(jù)的鎖進(jìn)行消除。
  3. 鎖粗化:如果虛擬機(jī)探測(cè)到一系列連續(xù)的操作對(duì)同一個(gè)對(duì)象頻繁加鎖,就會(huì)把加鎖的范圍擴(kuò)展到整個(gè)操作序列的外部。
  4. 輕量級(jí)鎖:相對(duì)于傳統(tǒng)的重量級(jí)鎖而言,使用CAS操作來(lái)避免重量級(jí)鎖使用互斥量的開(kāi)銷。先采用CAS操作進(jìn)行同步,如果CAS失敗了再改用互斥量來(lái)進(jìn)行同步。
  5. 偏向鎖:讓第一個(gè)獲取鎖對(duì)象的進(jìn)程,在這之后獲取該鎖就不再需要進(jìn)行同步操作。當(dāng)有另外一個(gè)鎖去嘗試獲取這個(gè)對(duì)象時(shí),偏向狀態(tài)就宣告結(jié)束,此時(shí)恢復(fù)到無(wú)鎖狀態(tài)或輕量級(jí)鎖狀態(tài)。
  • ReentrantLock
    基本語(yǔ)法上和synchronized相似,都是可重入鎖,但增加了一些高級(jí)功能,比如等待可中斷、可實(shí)現(xiàn)公平鎖、鎖可以綁定多個(gè)條件。

  • 二者的區(qū)別

  1. synchronized基于jvm實(shí)現(xiàn),ReentrantLock基于jdk實(shí)現(xiàn)
  2. ReentranLock是等待可中斷的,synchronized不是
  3. ReentranLock可實(shí)現(xiàn)公平鎖
  4. ReentranLock可綁定多個(gè)條件。
5.2 非阻塞同步

從處理問(wèn)題的方式上說(shuō),互斥同步屬于一種悲觀的并發(fā)策略,總是認(rèn)為只要不去做正確的同步措施,就會(huì)出現(xiàn)問(wèn)題,無(wú)論是否出現(xiàn)競(jìng)爭(zhēng),都要進(jìn)行加鎖。而樂(lè)觀鎖是先進(jìn)行操作,如果沒(méi)有其他線程競(jìng)爭(zhēng)共享數(shù)據(jù),那么操作就成功了。這種樂(lè)觀的并發(fā)策略不需要把線程掛起。

  • CAS
    比較并交換。CAS指令有3個(gè)操作數(shù),內(nèi)存地址V,舊的期望值A(chǔ),新的值B。只有當(dāng)V的值等于A,才把V的值更新為B。
  • JUC中的原子類
    例如AtomicInteger中就調(diào)用了Unsafe類的CAS操作。其自增操作就是基于CAS實(shí)現(xiàn)的。
  • ABA
    如果一個(gè)變量初次讀取的時(shí)候是A值,后來(lái)被改為了B,然后又被改回A,那CAS會(huì)誤認(rèn)為它沒(méi)有改變過(guò),這就會(huì)導(dǎo)致ABA問(wèn)題。JUC提供了一個(gè)帶有標(biāo)記的原子引用類AtomicStampedReference來(lái)解決這個(gè)問(wèn)題,它可以通過(guò)控制變量值的版本來(lái)保證CAS的正確性。

6.AQS(AbstractQueuedSynchronizer隊(duì)列同步器)

AQS是一個(gè)用來(lái)構(gòu)建鎖和同步器的框架,ReentrantLock,Semaphore,F(xiàn)utureTask等等均是基于AQS。其核心思想是,如果被請(qǐng)求的共享資源空閑,則將當(dāng)前請(qǐng)求資源的線程設(shè)置為有效的工作線程,并且將共享資源設(shè)置為鎖定狀態(tài)。如果被請(qǐng)求的共享資源被占用,需要一套線程阻塞等待以及喚醒時(shí)鎖分配的機(jī)制,這個(gè)機(jī)制是用CLH隊(duì)列鎖實(shí)現(xiàn)的,即將暫時(shí)獲取不到鎖的線程加入到隊(duì)列中。

CLH是一個(gè)虛擬的雙向隊(duì)列(不存在隊(duì)列實(shí)例,僅存在結(jié)點(diǎn)間的關(guān)聯(lián)聯(lián)系)。AQS將每條請(qǐng)求共享資源的線程封裝成一個(gè)CLH鎖隊(duì)列的一個(gè)結(jié)點(diǎn)來(lái)實(shí)現(xiàn)鎖的分配。

  • AQS定義兩種資源共享方式
    Exclusive(獨(dú)占):只有一個(gè)線程能執(zhí)行,如可重入鎖
    Share(共享):多個(gè)線程同時(shí)執(zhí)行,如Semaphore/CountDownLatch。

同步器的設(shè)計(jì)是基于模板方法模式的,也就是說(shuō),使用者需要繼承同步器并重寫(xiě)指定的方法。


同步器可重寫(xiě)的方法
  • AQS組件
  1. Semaphone:允許多個(gè)線程同時(shí)訪問(wèn)資源。
  2. CountDownLatch:用來(lái)控制一個(gè)線程等待多個(gè)線程。維護(hù)了一個(gè)計(jì)數(shù)器 cnt,每次調(diào)用 countDown() 方法會(huì)讓計(jì)數(shù)器的值減 1,減到 0 的時(shí)候,那些因?yàn)檎{(diào)用 await() 方法而在等待的線程就會(huì)被喚醒。
  3. CyclicBarrier:用來(lái)控制多個(gè)線程互相等待,只有當(dāng)多個(gè)線程都到達(dá)時(shí),這些線程才會(huì)繼續(xù)執(zhí)行。
  4. ReentrantReadWriteLock:讀寫(xiě)鎖允許同時(shí)對(duì)某一資源進(jìn)行讀

7.ThreadLocal

ThreadLocal對(duì)象可以提供線程局部變量,每個(gè)線程Thread擁有一份自己的副本變量,多個(gè)線程互不干擾。每個(gè)線程都有一個(gè)ThreadLocalMap,其結(jié)構(gòu)類似HashMap,但ThreadLocalMap中沒(méi)有鏈表結(jié)構(gòu)。每一個(gè)ThreadLocal對(duì)象作為Map中的key,value為代碼中放入的值。
其中的key為弱引用,發(fā)生GC后,key會(huì)被回收,而value為強(qiáng)引用,不會(huì)被回收,這個(gè)時(shí)候就可能導(dǎo)致內(nèi)存泄漏。ThreadLocalMap的解決方法為再調(diào)用set()、get()、remove()方法的時(shí)候,會(huì)清理掉key為Null的記錄。

8.線程池

http://m.itdecent.cn/p/3f6eed342491

9.場(chǎng)景

  • 死鎖
    public static Object lock1=new Object();
    public static Object lock2=new Object();

    public static void main(String[] args) {

        Thread thread1=new Thread(()->{

            synchronized (lock1){

                System.out.println("Thread 1 get Lock 1");

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    synchronized (lock2){
                        System.out.println("Thread 1 get Lock 2");
                    }
                }

            }

        });

        Thread thread2=new Thread(()->{

            synchronized (lock2){

                System.out.println("Thread 2 get Lock 2");

                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    synchronized (lock1){
                        System.out.println("Thread 2 get Lock 1");
                    }
                }

            }

        });

        thread1.start();
        thread2.start();


    }
  • 兩個(gè)線程交替打印A和B
    public static volatile boolean flag=true;

    public static void main(String[] args) {



        Thread thread1=new Thread(() -> {

            while (true){

                if (flag){
                    System.out.println("A");
                    flag=!flag;
                }

            }




        });
        Thread thread2=new Thread(() -> {

            while (true){

                if (!flag){
                    System.out.println("B");
                    flag=!flag;
                }

            }

        });

        thread1.start();
        thread2.start();


    }
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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