Java并發(fā)編程知識(shí)點(diǎn)梳理

一 并發(fā)編程基礎(chǔ)知識(shí)

1.1 概念

并發(fā)編程是你編寫(xiě)的代碼有可能在多線程環(huán)境中執(zhí)行,

1.2 為什么要用并發(fā)編程模型?
    1. 更加充分的利用多多個(gè)處理器的計(jì)算能力
    1. 使得應(yīng)用程序獲得更快的響應(yīng)時(shí)間
    1. 為程序員提供更好的編程模型
1.3 并發(fā)量是越大越好嗎?

程開(kāi)多了不一定好,因?yàn)橛芯€程上下文切換的時(shí)間開(kāi)銷(xiāo);有可能多線程程序時(shí)間開(kāi)銷(xiāo)更長(zhǎng)

  • 如何減少線程的上下文切換次數(shù)?
    • 無(wú)鎖并發(fā)編程;例如currenthashmap一定程度上就是采用這種策略
    • CAS算法:(campare and swap)
    • 使用最少的線程
    • 使用協(xié)程(單線程里面維持多個(gè)任務(wù)的切換)
1.4 Java的內(nèi)存模型JMM
JMM內(nèi)存模型

如上圖所示,每一個(gè)線程都保存有共享變量的一份備份,并發(fā)編程的關(guān)鍵之出就是在于怎么保證每一個(gè)現(xiàn)成的本地變量和主存中共享變量的值統(tǒng)一

1.5 順序一致性和指令重排序
  • 指令重排序:java 編譯器和cpu再保證單線程中最終語(yǔ)義不變的情況下為了提高程序的執(zhí)行效率會(huì)改變程序指令的執(zhí)行順序,
class RecorderExample{
  int a = 0;
  boolean flag = false;
  public void writer() {
    a = 1;                  //1 
    flag = true           //2 
  }
  public void reader() {
   if(flag) {               //3
    int i = a*a;          //4
  }
  }
}

如上程序,單線程環(huán)境重步驟1和步驟2完全是可以重排序的,但是在多線程環(huán)境下,步驟1和步驟2重排序就有可能會(huì)導(dǎo)致錯(cuò)誤。

  • 順序一致性
    • a) 一個(gè)線程中所有的操作必須按照程序的順序來(lái)執(zhí)行
    • b)所有線程都只能看到一個(gè)單一的操作執(zhí)行順序。
1.6 Happens-before簡(jiǎn)介

在JMM中如果一個(gè)操作的結(jié)果需要對(duì)另外一個(gè)操作可見(jiàn),那么這兩個(gè)操作之間必須存在happens-before關(guān)系; 這兩個(gè)操作既可以是在同一個(gè)線程中,也可以在不同的線程中。具體原則如下:

  • 1 程序順序規(guī)則:一個(gè)線程中每個(gè)操作happens-before于該線程中任意后續(xù)操作
  • 2 監(jiān)視器鎖規(guī)則:對(duì)一個(gè)鎖的解鎖happens-before于隨后對(duì)這個(gè)鎖的加鎖操作
  • 3 volatile變量規(guī)則:對(duì)一個(gè)volatile域的寫(xiě),happens-before于任意后續(xù)對(duì)這個(gè)volatile域的讀。
  • 4 傳遞性:如果A happens-before于B,且B happens-before C,那么A happens-before C
  • 5 start()規(guī)則:如果線程A執(zhí)行操作ThreadB.start()(啟動(dòng)線程B),那么A線程的ThreadB.start()(啟動(dòng)線程B)操作happens-before于線程B中的任意操作。
  • 6 join() 規(guī)則:如果線程A執(zhí)行操作ThreadB.join()并成功返回,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回。

二 Java如何實(shí)現(xiàn)并發(fā)編程

2.1 多線程的實(shí)現(xiàn)方式
  • 1 繼承Thread類創(chuàng)建線程 單繼承,多個(gè)線程不能共享同一個(gè)資源
  • 2 實(shí)現(xiàn)runnable接口 可以再繼承其他類,多個(gè)線程可以共享統(tǒng)一資源
  • 3 實(shí)現(xiàn)Callable接口
2.2 線程的狀態(tài)
  • 新建態(tài) New
  • 就緒態(tài) Ready
  • 運(yùn)行態(tài) Running
  • 死亡態(tài) Terminated
  • 阻塞態(tài) Blocked 等待鎖
  • 等待態(tài) Wating 需要等待其他線程做特定的操作(喚醒或者中斷)
  • 超時(shí)等待 TimedWaiting
    查看某一個(gè)進(jìn)程的狀態(tài),先通過(guò)jps查看所有的Java進(jìn)程,然后通過(guò)jstack分析查看每一個(gè)線程的狀態(tài)。
    線程狀切換圖
2.3 線程之間的通訊
  • 1 通過(guò)共享內(nèi)存

    • 1.1 volatile和sychrnized關(guān)鍵字
      • 由于線程有部分自己的存戶空間存儲(chǔ)共享變量的copy,所以看到的變量的值不一定是最新的。volatile關(guān)鍵修飾的變量可以告訴線程,訪問(wèn)某一個(gè)變量的值必須要去主存進(jìn)行讀取。
      • sychronized 修飾的方法或者代碼塊確保同一時(shí)刻只有一個(gè)線程執(zhí)行方法或者塊中的代碼,保證線程對(duì)變量的訪問(wèn)的可見(jiàn)性和排他性
        • 代碼塊:使用monitorenter和monitorexit指令實(shí)現(xiàn)加鎖
        • 方法:依靠方法上面的ACC_SYNCHRONIZED來(lái)完成
        • 本質(zhì)上都是對(duì)一個(gè)對(duì)象的監(jiān)視器(monitor)進(jìn)行獲取這個(gè)監(jiān)視器是排他的一次只能有一個(gè)線程獲取。
  • 2 通過(guò)信號(hào)(本質(zhì)上還是共享內(nèi)存)

    • 2.1 等待通知機(jī)制
      • 對(duì)象的wait()/notify
        • 釋放對(duì)象鎖,等待其他線程喚醒或者中斷
        • 通知一個(gè)/所有在對(duì)象上等待的線程,讓其從wait()方法中返回;前提是拿到對(duì)象的鎖
      • condition.await()/condition.signal()
        • 釋放lock鎖,等待其他線程喚醒或者中斷
        • 通知一個(gè)/所有在lock上等待的線程,讓其從await()方法中返回;前提是拿到對(duì)象的鎖
      • 經(jīng)典范式
        • 等待方
          • 獲取鎖對(duì)象
          • 如果條件不滿足,調(diào)用對(duì)象的wait()方法,被通知后仍然要檢查條件
          • 條件滿足則執(zhí)行相應(yīng)的邏輯
        • 通知方
          • 獲得對(duì)象的鎖
          • 改變條件
          • 通知所有等待在該對(duì)象上的線程
  • 3 join的使用

    • thread.join的含義:當(dāng)前線程A等待thread線程終止之后才從thread.join()返回

三 并發(fā)編程中如何保證線程的安全性

3.1 保證線程安全的含義
  • 可見(jiàn)性:保證某一個(gè)線程對(duì)共享資源的更該可以被其他線程看到
  • 原子性:保證某一個(gè)線程對(duì)共享資源的一次更新是不會(huì)被中斷的
3.2 Java保證線程安全實(shí)現(xiàn)方式
  • 1 通過(guò)鎖

    • 1.1 sychronized關(guān)鍵
      • a. 普通方法,鎖的是當(dāng)前調(diào)用該方法的實(shí)例
      • b. 對(duì)于靜態(tài)同步方法鎖的是,當(dāng)前的class對(duì)象
      • c. 對(duì)于同步代碼塊鎖的是Sychronized括號(hào)里面配置的對(duì)象

    JVM基于進(jìn)入和退出Monitor對(duì)象來(lái)實(shí)現(xiàn)方法和代碼塊同步的,兩者的實(shí)現(xiàn)細(xì)節(jié)不一樣;代碼塊同步使用的是,monitorenter和monitorexit指令實(shí)現(xiàn),方法同步通過(guò)另外一種方式實(shí)現(xiàn),但方法同步也可以通過(guò)該方式實(shí)現(xiàn)
    任何一個(gè)對(duì)象都有一個(gè)monitor與之關(guān)聯(lián),當(dāng)一個(gè)monitor被持有之后,它將處于鎖定狀態(tài)

    JVM鎖種類:偏向鎖;輕量級(jí)鎖--->CAS 機(jī)制實(shí)現(xiàn);互斥鎖 ---> CAS 機(jī)制實(shí)現(xiàn)

各種鎖的優(yōu)缺點(diǎn)
  • 1.2 Lock接口的實(shí)現(xiàn)類
    • 1.2.1 和sychronized的對(duì)象鎖區(qū)別
      • 嘗試非阻塞的方式獲取鎖 trylock()
      • 能夠被中斷的方式獲取鎖 lockInterruptibly()
      • 超時(shí)獲取鎖 tryLock(time)
      • 靈活性更高,自己是控制釋放
    • 1.2.2 隊(duì)列同步器AbstractQueuedSynchronizer
      • AbstractQueuedSynchronizer 用來(lái)構(gòu)建鎖和其他同步組件的基礎(chǔ)框架
      • 鎖是面向使用者的,定義了使用者和鎖交互的接口,隱藏了實(shí)現(xiàn)細(xì)節(jié),同步器面向的是鎖得實(shí)現(xiàn)者,簡(jiǎn)化了鎖的實(shí)現(xiàn)方式,屏蔽了同步狀態(tài)管理,線程的排隊(duì),等待和喚醒等底層操作,鎖和同步器很好的隔離了使用者和實(shí)現(xiàn)者所需要關(guān)注的領(lǐng)域。
      • 隊(duì)列同步器的實(shí)現(xiàn)
        • 通過(guò)一個(gè)內(nèi)部隊(duì)列實(shí)現(xiàn),同步器擁有這個(gè)隊(duì)列的頭結(jié)點(diǎn)和尾節(jié)點(diǎn),如果獲取鎖失敗則將該線程加入到同步隊(duì)列中
        • 獨(dú)占式同步狀態(tài)的獲取和釋放
          • 在獲取同步狀態(tài)時(shí),同步器維護(hù)一個(gè)同步隊(duì)列,獲取狀態(tài)失敗的線程都會(huì)被加入到這個(gè)隊(duì)列中并在隊(duì)列中進(jìn)行自旋 移出同步隊(duì)列(停止自旋)的條件是前驅(qū)節(jié)點(diǎn)為頭結(jié)點(diǎn)并且成功獲取了同步狀態(tài);
          • 釋放同步狀態(tài)時(shí),同步器調(diào)用tryReleases()方法,然后喚醒頭結(jié)點(diǎn)的后繼節(jié)點(diǎn)。
    • 1.2.3 Lock接口實(shí)現(xiàn)實(shí)現(xiàn)類圖
  • 2 通過(guò)volatile+cas操作
    • 2.1 volatile

      • 可見(jiàn)性,
        一個(gè)volatile變量的讀總能看到任意線程對(duì)這個(gè)變量的最后一次寫(xiě)入;
      • 原子性
        對(duì)任意單個(gè)volatile變量讀/寫(xiě)具有原子性,但類似于volatile++這種復(fù)合操作不具有原子性
      • volatile寫(xiě)的內(nèi)存語(yǔ)義
        當(dāng)寫(xiě)一個(gè)volatile變量時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存中的共享變量刷新到主存中
      • volatile讀的內(nèi)存內(nèi)存語(yǔ)義
        當(dāng)讀一個(gè)volatile變量時(shí),JMM會(huì)把該線程對(duì)應(yīng)的本地內(nèi)存置為無(wú)效,線程接下來(lái)將從主存中讀取共享變量
      • 通過(guò)插入內(nèi)存屏障禁止指令重排序達(dá)到目的
    • 2.2 CAS操作保證操作的原子性

      • 2.2.1 循環(huán)的進(jìn)行CAS操作指導(dǎo)成功為止,
      • 2.2.2 CAS 實(shí)現(xiàn)原子操作的三大問(wèn)題
        • A:ABA問(wèn)題:
          問(wèn)題描述:即線程A將共享變量的值從A變到B再?gòu)腂變到A,線程B認(rèn)為沒(méi)有發(fā)生變化
          解決辦法:增加版本號(hào)機(jī)制,每次更該版本號(hào)+1
        • B:循環(huán)時(shí)間長(zhǎng)開(kāi)銷(xiāo)大問(wèn)題
          如果JVM支持pause指令來(lái)解決
          a. 可以延遲流水線執(zhí)行指令,使得CPU不會(huì)消耗過(guò)多的資源
          b. 它可以避免在退出循環(huán)的時(shí)候,因內(nèi)存順序沖突而引起的CPU流水線被清空,從而提高CPU的執(zhí)行效率
        • C:只能保證一個(gè)共享變量的原子操作
          多個(gè)變量封裝成一個(gè)對(duì)象來(lái)解決

四 Java線程池的實(shí)現(xiàn)原理

1 為什么要使用線程池?
  • 降低資源的消耗
  • 提高響應(yīng)速度
  • 提高線程的可管理性
2 組成線程池的關(guān)鍵元素
線程池原理圖
  • maxinumPool 最大線程數(shù)
  • corePool 核心線程數(shù)
  • BlockingQueue<Runnable> 阻塞隊(duì)列用來(lái)存儲(chǔ)任務(wù)的
  • Time 線程的空轉(zhuǎn)最大時(shí)間
  • RejectedExecution : 當(dāng)線程數(shù)量達(dá)到最大線程數(shù)量,阻塞隊(duì)列也滿了,怎么處理新提交的任務(wù)
    • 1、直接丟棄(DiscardPolicy)
    • 2、丟棄隊(duì)列中最老的任務(wù)(DiscardOldestPolicy)。
    • 3、拋異常(AbortPolicy)
    • 4、將任務(wù)分給調(diào)用線程來(lái)執(zhí)行(CallerRunsPolicy)。
3 一個(gè)線程具體的提交流程如下:
線程提交流程圖
4 如何關(guān)閉線程池
  • shutdownNow()
    先將線程池設(shè)置成stop狀態(tài),遍歷線程調(diào)用每一個(gè)線程的Interrupt方法來(lái)中斷線程
  • shutDown()
    將線程池設(shè)置為SHUTDUWN狀態(tài),然后中斷所有沒(méi)有任務(wù)執(zhí)行的線程
5 合理配置線程池
  • 任務(wù)性質(zhì):CPU密集型,IO密集型
  • 任務(wù)的優(yōu)先級(jí):高中低
  • 任務(wù)的執(zhí)行時(shí)間長(zhǎng)度:長(zhǎng)中短
  • 任務(wù)的依賴性:是否依賴其他系統(tǒng)資源,

五 Executor框架簡(jiǎn)介

Java5開(kāi)始講線程的任務(wù)由原來(lái)的即負(fù)責(zé)工作單元也復(fù)制執(zhí)行機(jī)制拆分為,工作單元由:Runnable和Callable負(fù)責(zé),而執(zhí)行機(jī)制由Executor負(fù)責(zé)

5.1 Executor框架簡(jiǎn)介
Executor框架原理

Executor框架由三部分組成

  • 5.1.1 任務(wù):實(shí)現(xiàn)Runnable和Callable接口的任務(wù)
  • 5.1.2 任務(wù)的執(zhí)行:繼承自Executor接口的ExecutorService接口,框架又由兩個(gè)核心的實(shí)現(xiàn)接口(ThreadPoolExecutor和ScheduledThreadPoolExecutor)
    • 5.1.2.1 ThreadPoolExecutor
      一般使用工廠類Executors來(lái)創(chuàng)建,主要有三種類型的ThreadPoolExecutor

      • FixedThreadPool 創(chuàng)建固定線程數(shù)量的線程池
      • SingleThreadPoolExecutor 單個(gè)線程的線程池,順序地執(zhí)行各個(gè)任務(wù),
      • CacheThreadPool 大小無(wú)界的線程池:適用于很多短期異步任務(wù)的小程序
    • 5.1.2.1 ScheduledThreadPoolExecutor

      • ScheduledThreadPoolExecutor 包含若干線程的 ScheduledThreadPoolExecutor;適合多個(gè)后臺(tái)線程執(zhí)行周期任務(wù),同時(shí)為了滿足資源管理的需求而需要限制后臺(tái)線程的數(shù)量的應(yīng)用場(chǎng)景
      • SingleThreadScheduledPoolExecutor 單個(gè)后臺(tái)線程執(zhí)行周期任務(wù)
  • 5.1.3 異步計(jì)算的結(jié)果:Future和實(shí)現(xiàn)Future接口的FutureTask類


    Executor使用示意圖
5.2 ThreadPoolExecutor詳解

關(guān)鍵組成要素前一章節(jié)已經(jīng)闡述過(guò),這里主要闡述3種線程池的區(qū)別

  • FixedThreadPoolExecutor
    阻塞隊(duì)列LinkedBlockingQueue, 線程池中的線程不會(huì)達(dá)到corePoolSize
  • SingleThreadPoolExecutor
    LinkedBlockingQueue 無(wú)界隊(duì)列,
  • CacheThreadPoolExecutor
    使用SynchronouusQueue作為線程池的工作隊(duì)列,該隊(duì)列不存儲(chǔ)元素
5.3 ScheduledThreadPoolExecutor詳解
  • 用來(lái)執(zhí)行周期性任務(wù)
  • 使用delayQueue這個(gè)無(wú)界隊(duì)列來(lái)實(shí)現(xiàn),該隊(duì)列封裝了ProrityQueue會(huì)對(duì)隊(duì)列中的元素會(huì)進(jìn)行排序
  • 提交的任務(wù)為ScheduledFutureTask類型的任務(wù)


    ScheduledThreadPoolExecutor任務(wù)執(zhí)行步驟
5.4 FutureTast詳解
  • 1 FutureTask實(shí)現(xiàn)了Runable和Future兩個(gè)接口,有三種狀態(tài):
    • 未啟動(dòng)
    • 已啟動(dòng)
    • 已完成


      FutureTask狀態(tài)切換圖

      FutureTask處于未啟動(dòng)或者已啟動(dòng)時(shí)調(diào)用get方法將會(huì)導(dǎo)致調(diào)用點(diǎn)成阻塞,F(xiàn)utureTask處于完成狀態(tài)是則返回對(duì)應(yīng)的結(jié)果或者跑出異常

  • 2 FutureTask實(shí)現(xiàn)原理
    FutureTask是基于AbstractQueueSynchronizer同步器實(shí)現(xiàn)。


    FutureTask設(shè)計(jì)示意圖

參考文獻(xiàn)

《Java并發(fā)編程的藝術(shù)》

?著作權(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)容