Java高併發與多執行緒(一)-----概念

流年的夏天發表於2021-01-12

其實之前一直想專門寫一篇,單獨說一說Java的多執行緒與高併發,但是一直以來,都沒有想到能夠用什麼比較有趣的表現形式去表達出來,而且網上充斥著很多類似的部落格,有好的又不好的,有簡介的有繁瑣的,所以也一直沒寫。

但是想了想既然之前有這個想法,而且也已經好久沒有寫過部落格了,索性還是寫一寫,儘量寫的有意思一點。

   

另:之前的高併發&效能優化沒有來得及往下寫,實在是因為裡面的東西太過於複雜,且最近正好換了工作,確實沒有那麼多時間去研究,現在寫東西還是希望能多寫點有用的,而不是書本照搬當筆記用,當然能力有限,寫爛了,大家諒解一二,略過不看即可。

   

當然第一篇,我們依舊從概念開始,所以第一部分仍是概念。

   

【併發與並行】

從題目名詞開始講。

  • 併發

併發,顧名思義,一起出發;

在你吃飯的時候,來了一個電話,如果你可以先接電話,然後再繼續把飯吃完,這個叫併發;

但是如果你只能等飯吃完才可以去接電話,叫非併發(序列)。

所以,併發指的是處理多工的能力,當你只能一件事情一件事情序列執行任務的時候,就是不支援併發的,當你可以多件事情一起執行的時候(輪替或者其他方式),就是支援併發的。

   

  • 並行

還是舉上面那個例子,當你吃飯的時候,來了一個電話,你邊吃飯邊接電話,這叫並行;

並行指的就是同時執行;支援並行的基礎就是多執行緒。

   

【同步和非同步】

同步和非同步的概念一般用於方法

  • 同步  

當一個方法開始執行,必須等這個方法執行結束,才可以往下執行,我們叫做同步。

同步主要用於上下有遞進關係的程式碼,特點是有序,序列執行,邏輯簡單,但是執行效率較低。

  • 非同步     

當一個方法開始執行,我們不必等這個方法執行結束,直接執行後面的內容,我們叫做非同步。

(可以認為只是進行了一個訊息的傳遞,呼叫後會立即返回)

非同步在java裡面主要使用執行緒(包括一些封裝類也是如此)實現,特點是執行效率高,但是邏輯相對複雜,容易出問題

   

【什麼是高併發】

有果必有因,通俗來講,多執行緒可以認為是高併發的一種表現形式或者解決方案,所以在講多執行緒之前,我們先講高併發。

高併發,指的是一個系統,在短時間內,收到大量操作請求的情況。

這種情況,一般而言主要發生在web系統中,比如:京東雙十一,微博明星傳出緋聞,12306春運搶票等等。

 

很容易理解的東西我們不過多的作詮釋,以下幾項,是高併發的常用指標:

  • 響應時間Response Time

系統對請求作出的響應時間(一個請求從請求發出到請求結束的時間)

  • 吞吐量Throughput

單位時間內處理的請求數量

  • 每秒查詢率QPSQuery Per Second

每秒響應請求數。(其實與吞吐量指向同一個指標)

  • 併發使用者數

同時承載正常使用系統功能的使用者數量。

   

這裡需要注意的是,我們經常會將高併發和多執行緒放在一起講,但是他們之間並不能劃等號,多執行緒只是高併發在應用程式碼層面的一種解決方案,然而一般情況下,高併發還需要系統架構,硬體設施,網路等多方面的調優協助完成。

   

【雪崩效應】

雪崩效應,原本出現在密碼學中,後來引申入高併發場景的一個概念。

在密碼學中,雪崩效應(Avalanche effect)指加密演算法(尤其是塊密碼和加密雜湊函式)的一種理想屬性。

雪崩效應是指當輸入發生最微小的改變(例如,反轉一個二進位制位)時,也會導致輸出的劇變(如,輸出中一半的二進位制位發生反轉)。

   

服務雪崩效應是一種因"服務提供者的不可用"(原因)導致"服務呼叫者不可用"(結果),並將不可用逐漸放大的現象。

服務雪崩的過程可以分為三個階段:

  1. 服務提供者不可用;
  2. 重試加大請求流量;
  3. 服務呼叫者不可用;

   

------如何避免

橫向擴充服務------現在我們可以利用很多工具來保證服務不會掛掉,然後流量比較大的時候,可以橫向擴充服務來保證業務的流暢。

限流(下個部分會講)

熔斷(下個部分會講)

   

【高併發的四大利器】

對於軟體系統而言,一般會有四大策略去保證應用的高併發:

  • 快取(cache)

  把常用資料儲存到可以快速獲取的區域(快取區),以便重複利用,提高效率。

  例如:從記憶體中讀取資料時,先將常用的資料存放到快取區,硬碟直接從快取區讀取。

   

在這地地方我們要注意

我們平時所說的緩衝(buffer),和快取不是同一回事,緩衝指的是在資料流轉過程中,不同層次資料速度不一致時,利用緩衝區來緩解上下層之間速度問題,增加速度。

例如:將資料寫入到記憶體時,先寫入緩衝區,記憶體則直接從緩衝區中讀取寫入,減少IO次數,增加速度,降低對磁碟的損耗。

不過他們本質上都是為了提高效率

   

  • 降級

  當服務出現問題或影響到核心流程時,需要暫時遮蔽掉,待高峰過後或問題解決後再開啟;

   

  • 限流

  限流是高併發裡面最重要也是最複雜的方法,當不可降級場景出現時,需要採用限流限制該場景的併發請求,有損服務而不是不服務。

  通過對併發訪問/請求進行限速或者一個時間視窗內的的請求進行限速來保護系統,一旦達到限制速率則可以拒絕服務、排隊或等待、降級

    • 超過閾值時策略

    定向到錯誤頁或告知沒有資源

    返回兜底資料或預設資料,如商品詳情頁庫存預設有貨

    • 常見限流場景

    執行緒池

    資料庫連線池

    併發請求數

    介面呼叫速率

    MQ的消費速率

    • 常見限流演算法

    令牌桶:一個存放固定容量令牌的桶,按照固定速率往桶裡新增令牌,請求獲取令牌,令牌不足時拒絕請求。

    漏桶:流入速率過快,超過桶的容量,拒絕請求。

    計數器(簡單粗暴):當請求超過計數時,拒絕請求。

   

  • 熔斷

  降級往往代表系統功能部分不可用,熔斷代表的是完全不可用。
  降級一般是客戶端處理熔斷是在服務端處理的
  服務熔斷一般是指軟體系統中,由於某些原因使得服務出現了過載現象,為防止造成整個系統故障,從而採用的一種保護措施,所以很多地方把熔斷亦稱為過載保護

   

【程式】

首先我們看一下百度百科的解釋:

程式(Process)是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。

在早期面向程式設計的計算機結構中,程式是程式的基本執行實體

在當代面向執行緒設計的計算機結構中,程式是執行緒的容器

程式是指令、資料及其組織形式的描述,程式是程式的實體。

   

其實用大白話講,程式其實就是指在系統中正在執行的一個應用程式

比如:我們電腦中執行的QQ,微信,LOL,都是一個程式。

   

程式主要有以下幾個特性:

  • 獨立性

  程式是系統中獨立存在的實體,它擁有自己獨立的資源和自己私有的地址空間。

  程式之間不可以直接訪問資源和地址空間。

  • 動態性

  程式(App)是一個靜態的指令集和,程式是一個正在執行中的指令集合,程式擁有自己的生命週期和不同的生命形態。

  • 併發性

  多個程式可以在單個處理器上併發執行,不會相互受到影響。(主要是依賴於執行緒和時間片)

  一個程式裡面可以由單個或者多個執行緒協同 完成任務。

   

【什麼是多執行緒】

  • 首先,什麼是執行緒?

  教科書說法,執行緒是作業系統能夠進行運算排程的最小單元

  一個程式可以有多個執行緒,但是一個執行緒只有一個父程式;

  執行緒可以擁有自己的堆疊,程式計數器以及區域性變數,但是不擁有系統資源。

  

其實呢,學過作業系統大家都知道,其實對於單核單CPU而言,同時是隻能執行一個任務的,也就是說,同時只能跑一個執行緒;

如果我們們的CPU只能線性執行,就是當你執行一個執行緒的時候,這個執行緒可能要等待網路,IO等相關的資源,這個時候CPU只能等待,這樣CPU強大的運算能力就沒有得到發揮,所以,產生了一個時間片的概念;

    • 時間片

CPU給每個執行緒分配了一部分時間去執行,雖然CPU同時只能執行一個執行緒,但是我們進行執行緒的快速切換之後,可以模擬出一個CPU同時執行多個執行緒的場景(其實主要還是CPU太快了),這樣的話可以充分利用CPU計算速度快的優勢。(對於時間片,有很多種不同的演算法,有興趣可以百度,這裡不講)

在引入時間片以後,我們們一塊CPU就可以同時跑多個執行緒了。

   

所以什麼是多執行緒呢?

多執行緒指的是,在單個程式(或者程式)裡面,可以執行多個不同的執行緒,執行不同的任務,最終完成整個程式的執行邏輯。
 

這裡需要注意的是,執行緒是程式的子集,不同的程式使用不同的記憶體空間,而所有的執行緒共享一片相同的記憶體空間

別把它和棧記憶體搞混,每個執行緒都擁有單獨的棧記憶體用來儲存本地資料

   

【執行緒的狀態】

其實下面這些隨便找個教科書或者網上的教程都有,屬於廢話,但是為了概念的完整性,還是把它們貼在下面:

只需要關注帶顏色的內容

  • 新建狀態(New)

執行緒物件被建立後,就進入了新建狀態。

   

  • 就緒狀態(Runnable)

也被稱為"可執行狀態"。執行緒物件被建立後,其它執行緒呼叫了該物件的start()方法,從而來啟動該執行緒。

   

  • 執行狀態(Running)

執行緒獲取CPU許可權進行執行。需要注意的是,執行緒只能從就緒狀態進入到執行狀態

   

  • 阻塞狀態(Blocked)

阻塞狀態是執行緒因為某種原因放棄CPU使用權,暫時停止執行。
直到執行緒進入就緒狀態,才有機會轉到執行狀態。
- 等待阻塞

通過呼叫執行緒的wait()方法,讓執行緒等待某工作的完成。

- 同步阻塞

執行緒在獲取synchronized同步鎖失敗(因為鎖被其它執行緒所佔用),它會進入同步阻塞狀態。

- 其他阻塞

通過呼叫執行緒的sleep()或join()或發出了I/O請求時,執行緒會進入到阻塞狀態。

   

  • 死亡狀態(Dead)

執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。

   

【多執行緒三要素】

  •  原子性

即一個不可再被分割的顆粒。

在Java中原子性指的是一個或多個操作要麼全部執行成功要麼全部執行失敗

經典場景:張三向李四轉賬,扣錢和入錢操作,要麼全部完成,要麼全部完不成。

  • 有序性

程式按照程式碼的先後順序執行。

這個主要是因為CPU本身可能會對指令進行重排序,在某些需要嚴格控制順序的程式碼中,需要保持其有序。

  • 可見性

多個執行緒同時訪問某個變數的時候,其中一個執行緒對變數進行了修改,這個變數的新值可以馬上同步到另一個執行緒。

關於如何保障以上三要素,後面會講到。

   

關於高併發與多執行緒相關的概念,主要就是以上這些,在之後的內容中,會繼續寫到執行緒的實現方式和主要方法,鎖,多執行緒中的封裝類等相關內容,感謝。

相關文章