Java高並發與多線程(一)-----概念


其實之前一直想專門寫一篇,單獨說一說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本身可能會對指令進行重排序,在某些需要嚴格控制順序的代碼中,需要保持其有序。

  • 可見性

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

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

   

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM