05.java多執行緒問題

瀟湘劍雨發表於2018-12-24

目錄介紹

  • 5.0.0.1 執行緒池具有什麼優點和缺點?為什麼說開啟大量的執行緒,會降低程式的效能,那麼該如何做才能降低效能?
  • 5.0.0.3 執行緒中start和run方法有什麼區別?wait和sleep方法的不同?sleep() 、join()、yield()有什麼區別?
  • 5.0.0.4 用Java手寫一個會導致死鎖的程式,遇到這種問題解決方案是什麼?那些場景用到了死鎖機制?
  • 5.0.0.5 ThreadLocal(執行緒變數副本)這個類的作用是什麼?
  • 5.0.0.6 什麼是執行緒安全?執行緒安全有那幾個級別?保障執行緒安全有哪些手段?ReentrantLock和synchronized的區別?
  • 5.0.0.7 Volatile和Synchronized各自用途是什麼?有哪些不同點?Synchronize在編譯時如何實現鎖機制?
  • 5.0.0.8 wait()和sleep()的區別?各自有哪些使用場景?怎麼喚醒一個阻塞的執行緒?Thread.sleep(0)的作用是啥?
  • 5.0.0.9 同步和非同步、阻塞和非阻塞的概念?分別有哪些使用場景?
  • 5.0.1.0 執行緒的有哪些狀態?請繪製該狀態的流程圖?講一下執行緒的執行生命週期流程?執行緒如果出現了執行時異常會怎麼樣?
  • 5.0.1.1 synchronized鎖什麼?synchronized同步程式碼塊還有同步方法本質上鎖住的是誰?為什麼?
  • 5.0.1.2 Volatile實現原理?一個int變數,用volatile修飾,多執行緒去操作++,執行緒安全嗎?那如何才能保證i++執行緒安全?
  • 5.0.1.3 CAS原理是什麼?CAS實現原子操作會出現什麼問題?
  • 5.0.1.4 假如有n個網路執行緒,需要當n個網路執行緒完成之後,再去做資料處理,你會怎麼解決?
  • 5.0.1.5 Runnable介面和Callable介面的區別?
  • 5.0.1.6 如果提交任務時,執行緒池佇列已滿,這時會發生什麼?執行緒排程演算法是什麼?
  • 5.0.1.7 什麼是樂觀鎖和悲觀鎖?
  • 5.0.1.8 執行緒類的構造方法、靜態塊是被哪個執行緒呼叫的?同步方法和同步塊,哪個是更好的選擇?同步的範圍越少越好嗎?
  • 5.0.1.9 synchonized(this)和synchonized(object)區別?Synchronize作用於方法和靜態方法區別?

好訊息

  • 部落格筆記大彙總【15年10月到至今】,包括Java基礎及深入知識點,Android技術部落格,Python學習筆記等等,還包括平時開發中遇到的bug彙總,當然也在工作之餘收集了大量的面試題,長期更新維護並且修正,持續完善……開源的檔案是markdown格式的!同時也開源了生活部落格,從12年起,積累共計500篇[近100萬字],將會陸續發表到網上,轉載請註明出處,謝謝!
  • 連結地址:https://github.com/yangchong211/YCBlogs
  • 如果覺得好,可以star一下,謝謝!當然也歡迎提出建議,萬事起於忽微,量變引起質變!所有部落格將陸續開源到GitHub!

5.0.0.1 執行緒池具有什麼優點和缺點?為什麼說開啟大量的執行緒,會降低程式的效能,那麼該如何做才能降低效能?

  • 執行緒池好處:

  • 執行緒池的實現原理:

    • 當提交一個新任務到執行緒池時,判斷核心執行緒池裡的執行緒是否都在執行。如果不是,則建立一個新的執行緒執行任務。如果核心執行緒池的執行緒都在執行任務,則進入下個流程。
    • 判斷工作佇列是否已滿。如果未滿,則將新提交的任務儲存在這個工作佇列裡。如果工作佇列滿了,則進入下個流程。
    • 判斷執行緒池是否都處於工作狀態。如果沒有,則建立一個新的工作執行緒來執行任務。如果滿了,則交給飽和策略來處理這個任務。

5.0.0.3 執行緒中start和run方法有什麼區別?wait和sleep方法的不同?sleep() 、join()、yield()有什麼區別?

  • 執行緒中start和run方法有什麼區別

    • 為什麼我們呼叫start()方法時會執行run()方法,為什麼我們不能直接呼叫run()方法?這是一個非常經典的java多執行緒面試問題。當你呼叫start()方法時你將建立新的執行緒,並且執行在run()方法裡的程式碼。但是如果你直接呼叫run()方法,它不會建立新的執行緒也不會執行呼叫執行緒的程式碼。
  • wait和sleep方法的不同

    • 最大的不同是在等待時wait會釋放鎖,而sleep一直持有鎖。Wait通常被用於執行緒間互動,sleep通常被用於暫停執行。
  • 1、sleep()方法

    • 在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行),此操作受到系統計時器和排程程式精度和準確性的影響。 讓其他執行緒有機會繼續執行,但它並不釋放物件鎖。也就是如果有Synchronized同步塊,其他執行緒仍然不能訪問共享資料。注意該方法要捕獲異常
    • 比如有兩個執行緒同時執行(沒有Synchronized),一個執行緒優先順序為MAX_PRIORITY,另一個為MIN_PRIORITY,如果沒有Sleep()方法,只有高優先順序的執行緒執行完成後,低優先順序的執行緒才能執行;但當高優先順序的執行緒sleep(5000)後,低優先順序就有機會執行了。
    • 總之,sleep()可以使低優先順序的執行緒得到執行的機會,當然也可以讓同優先順序、高優先順序的執行緒有執行的機會。
  • 2、yield()方法技術部落格大總結

    • yield()方法和sleep()方法類似,也不會釋放“鎖標誌”,區別在於,它沒有引數,即yield()方法只是使當前執行緒重新回到可執行狀態,所以執行yield()的執行緒有可能在進入到可執行狀態後馬上又被執行,另外yield()方法只能使同優先順序或者高優先順序的執行緒得到執行機會,這也和sleep()方法不同。
  • 3、join()方法

    • Thread的非靜態方法join()讓一個執行緒B“加入”到另外一個執行緒A的尾部。在A執行完畢之前,B不能工作。
    • Thread t = new MyThread(); t.start(); t.join();保證當前執行緒停止執行,直到該執行緒所加入的執行緒完成為止。然而,如果它加入的執行緒沒有存活,則當前執行緒不需要停止。
  • Thread的join()有什麼作用?

    • Thread的join()的含義是等待該執行緒終止,即將掛起呼叫執行緒的執行,直到被呼叫的物件完成它的執行。比如存在兩個執行緒t1和t2,下述程式碼表示先啟動t1,直到t1的任務結束,才輪到t2啟動。
    t1.start();
    t1.join(); 
    t2.start();

5.0.0.4 用Java手寫一個會導致死鎖的程式,遇到這種問題解決方案是什麼?那些場景用到了死鎖機制?

  • 死鎖是怎麼一回事

    • 執行緒A和執行緒B相互等待對方持有的鎖導致程式無限死迴圈下去。
  • 深入理解死鎖的原理

    • 兩個執行緒裡面分別持有兩個Object物件:lock1和lock2。這兩個lock作為同步程式碼塊的鎖;
    • 執行緒1的run()方法中同步程式碼塊先獲取lock1的物件鎖,Thread.sleep(xxx),時間不需要太多,50毫秒差不多了,然後接著獲取lock2的物件鎖。這麼做主要是為了防止執行緒1啟動一下子就連續獲得了lock1和lock2兩個物件的物件鎖
    • 執行緒2的run)(方法中同步程式碼塊先獲取lock2的物件鎖,接著獲取lock1的物件鎖,當然這時lock1的物件鎖已經被執行緒1鎖持有,執行緒2肯定是要等待執行緒1釋放lock1的物件鎖的

5.0.0.5 ThreadLocal(執行緒變數副本)這個類的作用是什麼?

  • ThreadLocal即執行緒變數

    • ThreadLocal為每個執行緒維護一個本地變數。
    • 採用空間換時間,它用於執行緒間的資料隔離,它為每個使用該變數的執行緒提供獨立的變數副本,所以每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本。從執行緒的角度看,目標變數就象是執行緒的本地變數,這也是類名中“Local”所要表達的意思。ThreadLocal的實現是以ThreadLocal物件為鍵。任意物件為值得儲存結構。這個結構被附帶線上程上,也就是說一個執行緒可以根據一個ThreadLocal物件查詢到繫結在這個執行緒上的一個值。
  • ThreadLocal類是一個Map

    • ThreadLocal類中維護一個Map,用於儲存每一個執行緒的變數副本,Map中元素的鍵為執行緒物件,而值為對應執行緒的變數副本。
    • ThreadLocal在Spring中發揮著巨大的作用,在管理Request作用域中的Bean、事務管理、任務排程、AOP等模組都出現了它的身影。
    • Spring中絕大部分Bean都可以宣告成Singleton作用域,採用ThreadLocal進行封裝,因此有狀態的Bean就能夠以singleton的方式在多執行緒中正常工作了。
  • 更多詳細參考部落格:深入研究java.lang.ThreadLocal類

5.0.0.6 什麼是執行緒安全?執行緒安全有那幾個級別?保障執行緒安全有哪些手段?ReentrantLock和synchronized的區別?

  • 什麼是執行緒安全

    • 執行緒安全就是當多個執行緒訪問一個物件時,如果不用考慮這些執行緒在執行時環境下的排程和交替執行,也不需要進行額外的同步,或者在呼叫方進行任何其他的協調操作,呼叫這個物件的行為都可以獲得正確的結果,那這個物件是執行緒安全的。
  • 執行緒安全也是有幾個級別

    • 技術部落格大總結
    • 不可變:

      • 像String、Integer、Long這些,都是final型別的類,任何一個執行緒都改變不了它們的值,要改變除非新建立一個,因此這些不可變物件不需要任何同步手段就可以直接在多執行緒環境下使用
    • 絕對執行緒安全

      • 不管執行時環境如何,呼叫者都不需要額外的同步措施。要做到這一點通常需要付出許多額外的代價,Java中標註自己是執行緒安全的類,實際上絕大多數都不是執行緒安全的,不過絕對執行緒安全的類,Java中也有,比方說CopyOnWriteArrayList、CopyOnWriteArraySet
    • 相對執行緒安全

      • 相對執行緒安全也就是我們通常意義上所說的執行緒安全,像Vector這種,add、remove方法都是原子操作,不會被打斷,但也僅限於此,如果有個執行緒在遍歷某個Vector、有個執行緒同時在add這個Vector,99%的情況下都會出現ConcurrentModificationException,也就是fail-fast機制。
    • 執行緒非安全技術部落格大總結

      • ArrayList、LinkedList、HashMap等都是執行緒非安全的類.
  • 保障執行緒安全有哪些手段。保證執行緒安全可從多執行緒三特性出發:

    • 原子性(Atomicity):單個或多個操作是要麼全部執行,要麼都不執行

      • Lock:保證同時只有一個執行緒能拿到鎖,並執行申請鎖和釋放鎖的程式碼
      • synchronized:對執行緒加獨佔鎖,被它修飾的類/方法/變數只允許一個執行緒訪問
    • 可見性(Visibility):當一個執行緒修改了共享變數的值,其他執行緒能夠立即得知這個修改

      • volatile:保證新值能立即同步到主記憶體,且每次使用前立即從主記憶體重新整理;
      • synchronized:在釋放鎖之前會將工作記憶體新值更新到主存中
    • 有序性(Ordering):程式程式碼按照指令順序執行

      • volatile: 本身就包含了禁止指令重排序的語義
      • synchronized:保證一個變數在同一個時刻只允許一條執行緒對其進行lock操作,使得持有同一個鎖的兩個同步塊只能序列地進入
  • ReentrantLock和synchronized的區別

    • ReentrantLock與synchronized的不同在於ReentrantLock:

      • 等待可中斷:當持有鎖的執行緒長期不釋放鎖的時候,正在等待的執行緒可以選擇放棄等待,改為處理其他事情。
      • 公平鎖:多個執行緒在等待同一個鎖時,必須按照申請鎖的時間順序來依次獲得鎖。而synchronized是非公平的,即在鎖被釋放時,任何一個等待鎖的執行緒都有機會獲得鎖。ReentrantLock預設情況下也是非公平的,但可以通過帶布林值的建構函式改用公平鎖。
      • 鎖繫結多個條件:一個ReentrantLock物件可以通過多次呼叫newCondition()同時繫結多個Condition物件。而在synchronized中,鎖物件wait()和notify()或notifyAl()只能實現一個隱含的條件,若要和多於一個的條件關聯不得不額外地新增一個鎖。
    • Synchronized是悲觀鎖機制,獨佔鎖。而Locks.ReentrantLock是,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因為衝突失敗就重試,直到成功為止。

      • ReentrantLock適用場景
      • 某個執行緒在等待一個鎖的控制權的這段時間需要中斷
      • 需要分開處理一些wait-notify,ReentrantLock裡面的Condition應用,能夠控制notify哪個執行緒,鎖可以繫結多個條件。
      • 具有公平鎖功能,每個到來的執行緒都將排隊等候。
    • 更多詳細參考部落格:Lock與synchronized 的區別

5.0.0.7 Volatile和Synchronized各自用途是什麼?有哪些不同點?Synchronize在編譯時如何實現鎖機制?

  • Volatile和Synchronized各自用途是什麼?有哪些不同點?

    • 1 粒度不同,前者針對變數 ,後者鎖物件和類
    • 2 syn阻塞,volatile執行緒不阻塞
    • 3 syn保證三大特性,volatile不保證原子性
    • 4 syn編譯器優化,volatile不優化 volatile具備兩種特性:

      • 1.保證此變數對所有執行緒的可見性,指一條執行緒修改了這個變數的值,新值對於其他執行緒來說是可見的,但並不是多執行緒安全的。
      • 2.禁止指令重排序優化。
    • Volatile如何保證記憶體可見性:

      • 1.當寫一個volatile變數時,JMM會把該執行緒對應的本地記憶體中的共享變數重新整理到主記憶體。
      • 2.當讀一個volatile變數時,JMM會把該執行緒對應的本地記憶體置為無效。執行緒接下來將從主記憶體中讀取共享變數。
    • 同步:就是一個任務的完成需要依賴另外一個任務,只有等待被依賴的任務完成後,依賴任務才能完成。
    • 非同步:不需要等待被依賴的任務完成,只是通知被依賴的任務要完成什麼工作,只要自己任務完成了就算完成了,被依賴的任務是否完成會通知回來。(非同步的特點就是通知)。 打電話和發簡訊來比喻同步和非同步操作。
    • 阻塞:CPU停下來等一個慢的操作完成以後,才會接著完成其他的工作。
    • 非阻塞:非阻塞就是在這個慢的執行時,CPU去做其他工作,等這個慢的完成後,CPU才會接著完成後續的操作。
    • 非阻塞會造成執行緒切換增加,增加CPU的使用時間能不能補償系統的切換成本需要考慮。
  • Synchronize在編譯時如何實現鎖機制?

    • Synchronized進過編譯,會在同步塊的前後分別形成monitorenter和monitorexit這個兩個位元組碼指令。在執行monitorenter指令時,首先要嘗試獲取物件鎖。如果這個物件沒被鎖定,或者當前執行緒已經擁有了那個物件鎖,把鎖的計算器加1,相應的,在執行monitorexit指令時會將鎖計算器就減1,當計算器為0時,鎖就被釋放了。如果獲取物件鎖失敗,那當前執行緒就要阻塞,直到物件鎖被另一個執行緒釋放為止。

5.0.0.8 wait()和sleep()的區別?各自有哪些使用場景?怎麼喚醒一個阻塞的執行緒?Thread.sleep(0)的作用是啥?

  • sleep來自Thread類,和wait來自Object類

    • 呼叫sleep()方法的過程中,執行緒不會釋放物件鎖。而呼叫wait方法執行緒會釋放物件鎖
    • sleep睡眠後不出讓系統資源,wait讓出系統資源其他執行緒可以佔用CPU
    • sleep(milliseconds)需要指定一個睡眠時間,時間一到會自動喚醒
  • 通俗解釋

    • Java程式中wait 和 sleep都會造成某種形式的暫停,它們可以滿足不同的需要。wait()方法用於執行緒間通訊,如果等待條件為真且其它執行緒被喚醒時它會釋放鎖,而 sleep()方法僅僅釋放CPU資源或者讓當前執行緒停止執行一段時間,但不會釋放鎖。
  • 怎麼喚醒一個阻塞的執行緒?

    • 如果執行緒是因為呼叫了wait()、sleep()或者join()方法而導致的阻塞,可以中斷執行緒,並且通過丟擲InterruptedException來喚醒它;如果執行緒遇到了IO阻塞,無能為力,因為IO是作業系統實現的,Java程式碼並沒有辦法直接接觸到作業系統。
    • 技術部落格大總結
  • Thread.sleep(0)的作用是啥?

    • 由於Java採用搶佔式的執行緒排程演算法,因此可能會出現某條執行緒常常獲取到CPU控制權的情況,為了讓某些優先順序比較低的執行緒也能獲取到CPU控制權,可以使用Thread.sleep(0)手動觸發一次作業系統分配時間片的操作,這也是平衡CPU控制權的一種操作。

5.0.0.9 同步和非同步、阻塞和非阻塞的概念?分別有哪些使用場景?

  • 同步和非同步

    • 同步和非同步體現的是訊息的通知機制:所謂同步,方法A呼叫方法B後必須等到方法B返回結果才能繼續後面的操作;所謂非同步,方法A呼叫方法B後可讓方法B在呼叫結束後通過回撥等方式通知方法A
  • 阻塞和非阻塞

    • 阻塞和非阻塞側重於等待訊息時的狀態:所謂阻塞,就是在結果返回之前讓當前執行緒掛起;所謂非阻塞,就是在等待時可做其他事情,通過輪詢去詢問是否已返回結果

5.0.1.0 執行緒的有哪些狀態?請繪製該狀態的流程圖?講一下執行緒的執行生命週期流程?執行緒如果出現了執行時異常會怎麼樣?

  • 在任意一個時間點,一個執行緒只能有且只有其中的一種狀態

    • 新建(New):執行緒建立後尚未啟動
    • 技術部落格大總結
    • 執行(Runable):包括正在執行(Running)和等待著CPU為它分配執行時間(Ready)兩種
      無限期等待(Waiting):該執行緒不會被分配CPU執行時間,要等待被其他執行緒顯式地喚醒。以下方法會讓執行緒陷入無限期等待狀態:
    沒有設定Timeout引數的Object.wait()
    沒有設定Timeout引數的Thread.join()
    LockSupport.park()
    • 限期等待(Timed Waiting):該執行緒不會被分配CPU執行時間,但在一定時間後會被系統自動喚醒。以下方法會讓執行緒進入限期等待狀態:
    Thread.sleep()
    設定了Timeout引數的Object.wai()
    設定了Timeout引數的Thread.join()
    LockSupport.parkNanos()
    LockSupport.parkUntil()
    • 阻塞(Blocked):執行緒被阻塞。和等待狀態不同的是,阻塞狀態表示在等待獲取到一個排他鎖,在另外一個執行緒放棄這個鎖的時候發生;而等待狀態表示在等待一段時間或者喚醒動作的發生,在程式等待進入同步區域的時候發生。
    • 結束(Terminated):執行緒已經結束執行
  • 繪製該狀態的流程圖
  • 執行緒如果出現了執行時異常會怎麼樣?

    • 如果這個異常沒有被捕獲的話,這個執行緒就停止執行了。另外重要的一點是:如果這個執行緒持有某個某個物件的監視器,那麼這個物件監視器會被立即釋放

5.0.1.1 synchronized鎖什麼?synchronized同步程式碼塊還有同步方法本質上鎖住的是誰?為什麼?

  • synchronized鎖什麼

    • 對於普通同步方法,鎖是當前例項物件;
    • 對於靜態同步方法,鎖是當前類的Class物件;
    • 對於同步方法塊,鎖是括號中配置的物件;
    • 當一個執行緒試圖訪問同步程式碼塊時,它首先必須得到鎖,退出或丟擲異常時必須釋放鎖。synchronized用的鎖是存在Java物件頭裡的MarkWord,通常是32bit或者64bit,其中最後2bit表示鎖標誌位。
  • 本質上鎖住的是物件。

    • 在java虛擬機器中,每個物件和類在邏輯上都和一個監視器相關聯,synchronized本質上是對一個物件監視器的獲取。當執行同步程式碼塊或同步方法時,執行方法的執行緒必須先獲得該物件的監視器,才能進入同步程式碼塊或同步方法;而沒有獲取到的執行緒將會進入阻塞佇列,直到成功獲取物件監視器的執行緒執行結束並釋放鎖後,才會喚醒阻塞佇列的執行緒,使其重新嘗試對物件監視器的獲取。

5.0.1.2 Volatile實現原理?一個int變數,用volatile修飾,多執行緒去操作++,執行緒安全嗎?那如何才能保證i++執行緒安全?

  • volatile的作用和原理

    • Java程式碼在編譯後會變成Java位元組碼,位元組碼被類載入器載入到JVM裡,JVM執行位元組碼,最終需要轉化為彙編指令在CPU上執行。
    • volatile是輕量級的synchronized(volatile不會引起執行緒上下文的切換和排程),它在多處理器開發中保證了共享變數的“可見性”。可見性的意思是當一個執行緒修改一個共享變數時,另外一個執行緒能讀到這個修改的值。
    • 由於記憶體訪問速度遠不及CPU處理速度,為了提高處理速度,處理器不直接和記憶體進行通訊,而是先將系統記憶體的資料讀到內部快取後在進行操作,但操作完不知道何時會寫到記憶體。普通共享變數被修改之後,什麼時候被寫入主存是不確定的,當其他執行緒去讀取時,此時記憶體中可能還是原來的舊值,因此無法保證可見性。如果對宣告瞭volatile的變數進行寫操作,JVM就會想處理器傳送一條Lock字首的指令,表示將當前處理器快取行的資料寫回到系統記憶體。
  • 一個int變數,用volatile修飾,多執行緒去操作++,執行緒安全嗎

    • 技術部落格大總結
    • 不安全
    • 案例程式碼,至於列印結果就不展示呢
    • volatile只能保證可見性,並不能保證原子性。
    • i++實際上會被分成多步完成:

      • 1)獲取i的值;
      • 2)執行i+1;
      • 3)將結果賦值給i。
    • volatile只能保證這3步不被重排序,多執行緒情況下,可能兩個執行緒同時獲取i,執行i+1,然後都賦值結果2,實際上應該進行兩次+1操作。
    private volatile int a = 0;
    for (int x=0 ; x<=100 ; x++){
        new Thread(new Runnable() {
            @Override
            public void run() {
                a++;
                Log.e("小楊逗比Thread-------------",""+a);
            }
        }).start();
    }
  • 如何才能保證i++執行緒安全

    • 可以使用java.util.concurrent.atomic包下的原子類,如AtomicInteger。其實現原理是採用CAS自旋操作更新值。
    for (int x=0 ; x<=100 ; x++){
        new Thread(new Runnable() {
            @Override
            public void run() {
                AtomicInteger atomicInteger = new AtomicInteger(a++);
                int i = atomicInteger.get();
                Log.e("小楊逗比Thread-------------",""+i);
            }
        }).start();
    }

5.0.1.3 CAS原理是什麼?CAS實現原子操作會出現什麼問題?

  • CAS原理是什麼

    • CAS即compare and swap的縮寫,中文翻譯成比較並交換。CAS有3個運算元,記憶體值V,舊的預期值A,要修改的新值B。當且僅當預期值A和記憶體值V相同時,將記憶體值V修改為B,否則什麼都不做。自旋就是不斷嘗試CAS操作直到成功為止。
  • CAS實現原子操作會出現什麼問題

    • ABA問題。因為CAS需要在操作之的時候,檢查值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成,有變成A,那麼使用CAS進行檢查時會發現它的值沒有發生變化,但實際上發生了變化。ABA問題可以通過新增版本號來解決。Java 1.5開始,JDK的Atomic包裡提供了一個類AtomicStampedReference來解決ABA問題。
    • 迴圈時間長開銷大。pause指令優化。
    • 只能保證一個共享變數的原子操作。可以合併成一個物件進行CAS操作。

5.0.1.4 假如有n個網路執行緒,需要當n個網路執行緒完成之後,再去做資料處理,你會怎麼解決?

  • 多執行緒同步的問題。這種情況可以可以使用thread.join();join方法會阻塞直到thread執行緒終止才返回。更復雜一點的情況也可以使用CountDownLatch,CountDownLatch的構造接收一個int引數作為計數器,每次呼叫countDown方法計數器減一。做資料處理的執行緒呼叫await方法阻塞直到計數器為0時。

5.0.1.5 Runnable介面和Callable介面的區別?

  • Runnable介面和Callable介面的區別

    • Runnable介面中的run()方法的返回值是void,它做的事情只是純粹地去執行run()方法中的程式碼而已;Callable介面中的call()方法是有返回值的,是一個泛型,和Future、FutureTask配合可以用來獲取非同步執行的結果。
    • 這其實是很有用的一個特性,因為多執行緒相比單執行緒更難、更復雜的一個重要原因就是因為多執行緒充滿著未知性,某條執行緒是否執行了?某條執行緒執行了多久?某條執行緒執行的時候我們期望的資料是否已經賦值完畢?無法得知,我們能做的只是等待這條多執行緒的任務執行完畢而已。而Callable+Future/FutureTask卻可以獲取多執行緒執行的結果,可以在等待時間太長沒獲取到需要的資料的情況下取消該執行緒的任務,真的是非常有用。

5.0.1.6 如果提交任務時,執行緒池佇列已滿,這時會發生什麼?執行緒排程演算法是什麼?

  • 如果提交任務時,執行緒池佇列已滿,這時會發生什麼?

    • 如果使用的是無界佇列LinkedBlockingQueue,也就是無界佇列的話,沒關係,繼續新增任務到阻塞佇列中等待執行,因為LinkedBlockingQueue可以近乎認為是一個無窮大的佇列,可以無限存放任務
    • 技術部落格大總結
    • 如果使用的是有界佇列比如ArrayBlockingQueue,任務首先會被新增到ArrayBlockingQueue中,ArrayBlockingQueue滿了,會根據maximumPoolSize的值增加執行緒數量,如果增加了執行緒數量還是處理不過來,ArrayBlockingQueue繼續滿,那麼則會使用拒絕策略RejectedExecutionHandler處理滿了的任務,預設是AbortPolicy
  • 執行緒排程演算法是什麼?

    • 搶佔式。一個執行緒用完CPU之後,作業系統會根據執行緒優先順序、執行緒飢餓情況等資料算出一個總的優先順序並分配下一個時間片給某個執行緒執行。

5.0.1.7 什麼是樂觀鎖和悲觀鎖?

  • 什麼是樂觀鎖和悲觀鎖?

    • 樂觀鎖:就像它的名字一樣,對於併發間操作產生的執行緒安全問題持樂觀狀態,樂觀鎖認為競爭不總是會發生,因此它不需要持有鎖,將比較-替換這兩個動作作為一個原子操作嘗試去修改記憶體中的變數,如果失敗則表示發生衝突,那麼就應該有相應的重試邏輯。
    • 悲觀鎖:還是像它的名字一樣,對於併發間操作產生的執行緒安全問題持悲觀狀態,悲觀鎖認為競爭總是會發生,因此每次對某資源進行操作時,都會持有一個獨佔的鎖,就像synchronized,直接上了鎖就操作資源。

5.0.1.8 執行緒類的構造方法、靜態塊是被哪個執行緒呼叫的?同步方法和同步塊,哪個是更好的選擇?同步的範圍越少越好嗎?

  • 執行緒類的構造方法、靜態塊是被哪個執行緒呼叫的?

    • 執行緒類的構造方法、靜態塊是被new這個執行緒類所在的執行緒所呼叫的,而run方法裡面的程式碼才是被執行緒自身所呼叫的。
  • 舉個例子

    • 假設Thread2中new了Thread1,main函式中new了Thread2,那麼:

      • Thread2的構造方法、靜態塊是main執行緒呼叫的,Thread2的run()方法是Thread2自己呼叫的
      • Thread1的構造方法、靜態塊是Thread2呼叫的,Thread1的run()方法是Thread1自己呼叫的
  • 同步方法和同步塊,哪個是更好的選擇?

    • 同步塊,這意味著同步塊之外的程式碼是非同步執行的,這比同步整個方法更提升程式碼的效率。請知道一條原則:同步的範圍越小越好。
    • 技術部落格大總結
  • 同步的範圍越少越好嗎?

    • 是的。雖說同步的範圍越少越好,但是在Java虛擬機器中還是存在著一種叫做鎖粗化的優化方法,這種方法就是把同步範圍變大。這是有用的,比方說StringBuffer,它是一個執行緒安全的類,自然最常用的append()方法是一個同步方法,我們寫程式碼的時候會反覆append字串,這意味著要進行反覆的加鎖->解鎖,這對效能不利,因為這意味著Java虛擬機器在這條執行緒上要反覆地在核心態和使用者態之間進行切換,因此Java虛擬機器會將多次append方法呼叫的程式碼進行一個鎖粗化的操作,將多次的append的操作擴充套件到append方法的頭尾,變成一個大的同步塊,這樣就減少了加鎖–>解鎖的次數,有效地提升了程式碼執行的效率。

5.0.1.9 synchonized(this)和synchonized(object)區別?Synchronize作用於方法和靜態方法區別?

  • synchonized(this)和synchonized(object)區別技術部落格大總結

    • 其實並沒有很大的區別,synchonized(object)本身就包含synchonized(this)這種情況,使用的場景都是對一個程式碼塊進行加鎖,效率比直接在方法名上加synchonized高一些(下面分析),唯一的區別就是物件的不同。
    • 對synchronized(this)的一些理解

      • 一、當兩個併發執行緒訪問同一個物件object中的這個synchronized(this)同步程式碼塊時,一個時間內只能有一個執行緒得到執行。另一個執行緒必須等待當前執行緒執行完這個程式碼塊以後才能執行該程式碼塊。
      • 二、然而,當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,另一個執行緒仍然可以訪問該object中的非synchronized(this)同步程式碼塊。
      • 三、尤其關鍵的是,當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,其他執行緒對object中所有其它synchronized(this)同步程式碼塊的訪問將被阻塞。
      • 四、當一個執行緒訪問object的一個synchronized(this)同步程式碼塊時,它就獲得了這個object的物件鎖。結果,其它執行緒對該object物件所有同步程式碼部分的訪問都被暫時阻塞。
  • Synchronize作用於方法和靜態方法區別

    • 測試程式碼如下所示
    private void test() {
        final TestSynchronized test1 = new TestSynchronized();
        final TestSynchronized test2 = new TestSynchronized();
        Thread t1 = new Thread(new Runnable() {
    
            @Override
            public void run() {
                test1.method01("a");
                //test1.method02("a");
            }
        });
        Thread t2 = new Thread(new Runnable() {
    
            @Override
            public void run() {
                test2.method01("b");
                //test2.method02("a");
            }
        });
        t1.start();
        t2.start();
    }
    
    private static class TestSynchronized{
        private int num1;
        public synchronized void method01(String arg) {
            try {
                if("a".equals(arg)){
                    num1 = 100;
                    System.out.println("tag a set number over");
                    Thread.sleep(1000);
                }else{
                    num1 = 200;
                    System.out.println("tag b set number over");
                }
                System.out.println("tag = "+ arg + ";num ="+ num1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        private static int  num2;
        public static synchronized void method02(String arg) {
            try {
                if("a".equals(arg)){
                    num2 = 100;
                    System.out.println("tag a set number over");
                    Thread.sleep(1000);
                }else{
                    num2 = 200;
                    System.out.println("tag b set number over");
                }
                System.out.println("tag = "+ arg + ";num ="+ num2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    //呼叫method01方法列印日誌【普通方法】
    tag a set number over
    tag b set number over
    tag = b;num =200
    tag = a;num =100
    
    
    //呼叫method02方法列印日誌【static靜態方法】
    tag a set number over
    tag = a;num =100
    tag b set number over
    tag = b;num =200
    • 在static方法前加synchronized:靜態方法屬於類方法,它屬於這個類,獲取到的鎖,是屬於類的鎖。
    • 在普通方法前加synchronized:非static方法獲取到的鎖,是屬於當前物件的鎖。 技術部落格大總結
    • 結論:類鎖和物件鎖不同,synchronized修飾不加static的方法,鎖是加在單個物件上,不同的物件沒有競爭關係;修飾加了static的方法,鎖是載入類上,這個類所有的物件競爭一把鎖。

其他介紹

01.關於部落格彙總連結

02.關於我的部落格


相關文章