引言
在學習并發以前,我們習慣用一種簡單的順序敘事方式編程,首先是第一件事,然后是第二件事,第三件 ...... 總之,我們完全掌握著事情的發展,現在,我們來到了陌生的并發世界,你會發現原本值得信賴的事物突然變得不可靠了,比如將一個值設為 5,回頭一看又變成 47 了,這就很匪夷所思了,并發就好似使用多故事線并行的敘事方式寫的一部小說:第一個間諜在巖石底留下微縮膠片,第二個間諜來取時,膠片已經被第三個間諜拿走了,然而小說并沒有交代此處的細節,所以直到故事結尾,我們都沒能搞清楚發生了什么?
并行和并發
并行和并發經常被混為一談,實際上兩者是不同的概念:
- 并發是關于正確有效地控制共享資源的訪問
- 并行是使用額外的資源來更快地產生結果
并發表示同時完成多個任務,無需等待當前任務完成即可執行其他任務,并發解決了程式因外部控制而無法進一步往下執行的阻塞問題,最常見的例子就是 I/O 操作,任務必須等待資料輸入,
并行表示同時在多個位置完成多個任務,將程式分為多部分,在多個處理器上同時處理不同部分以加快程式執行效率,
從定義上看,并發和并行都是“同時完成多個任務”,不過并行增加了跨多個處理器的分布,并發和并行用來解決不同型別的問題,并行可能對于 I/O 密集型問題沒有任何增益,因為問題不在于程式的整體執行速度,而在于 I/O 阻塞,而嘗試在單個處理器上使用并發來解決計算密集型問題可能也是浪費時間,實際上,兩者都是為了能在更短的時間內完成更多的作業,但它們各自實作的方式有所不同,這取決于問題施加的約束,
這兩個概念經常被混在一起的原因主要是包括 Java 在內的許多編程語言都使用相同的機制 —— 執行緒來實作并行和并發,
并發的新定義
并發是一系列專注于減少等待的性能技術
這實際上是一個相當復雜的表述,所以可以將其分解如下:
- 并發技術包含了許多不同的方法來解決這個問題,這使得并發十分具有挑戰性,
- 并發的目的在于讓你的程式運行得更快,然而它的使用和管理十分棘手,如非必要,不要隨意使用,
- 并發的關鍵是減少等待,因為你只有在等待發生時才能收益,如果你發起一個 I/O 請求后能立即獲得結果,沒有延遲,那么就無需改進,如果程式的某個部分被迫等待,才有必要使用并發來加快速度,
想象這么一個場景,你必須在一棟大樓找某樣東西,而這個東西被巧妙地隱藏在大樓中一千萬個房間中的一間,顯然,如果你一個人挨家挨戶地找,恐怕一輩子也找不完,
假設你有某種超能力,可以像火影忍者一樣變出影分身,那好,你變出一千萬個自己,每個人搜索一個房間,這樣效率自然奇高,但我們回到現實,把一千萬個分身換成一千萬個處理器核心,顯然這就不可能辦到了,
我們再增加一個條件:每個分身到達一扇門時,必須敲門等待,直到有人開門,如果每個分身都有一個處理器核心,那沒有問題,等就是了,但事實上,我們的機器可能只有八個處理器核心,卻有一千萬個分身,那好吧,只能每個分身輪流用了,同時,我們不希望處理器因為某個分身剛好在等待應答時被鎖住而閑置下來,處理器應該能切換到別的分身上繼續執行任務,必要的時候再回來,這就是所謂的減少等待,
并發為速度而生
上面的場景可以簡單理解為:處理器從等待(阻塞)的任務切換到準備運行的任務,就大程度利用空閑下來的運算資源,但切換任務是有成本的(創建和切換背景關系的開銷),也許這會導致你的程式比原來還要慢也說不定,減少背景關系切換的方法有無鎖并發編程、CAS 演算法、使用最少執行緒和使用協程:
- 無鎖并發編程:當多執行緒競爭鎖時,會引起背景關系切換,所以我們可以避免使用鎖,如將資料 ID 按 Hash 演算法分段,不同執行緒處理不同段的資料
- CAS 演算法:Java 提供的 Atomic 包使用 CAS 演算法來更新資料,其實作不需要加鎖
- 使用最少執行緒:避免創建不必要的執行緒
- 協程:在單執行緒里實作多任務的調度,并在單執行緒里維持多個任務間的切換
并發執行的任務有時會互相干擾,比如一個制作蛋糕的工廠,每個工人各司其職,最后蛋糕完成了,一個工人準備吧蛋糕放進盒子里,突然另一個工人沖了過來,也把蛋糕放進盒子里,啪的一聲,兩個蛋糕撞到一起摔壞了,這就是常見的共享記憶體問題,即所謂的競爭狀態,其結果取決于哪個工人先把蛋糕放進盒子,這個問題通常使用鎖機制解決,比如一個工人先把盒子抱住,以免別人沖過來把蛋糕砸壞,
并發是如此的危險,所以在決定使用它之前要仔細思考,不要隨便跳進并發的火坑,如果有別的方法可以讓你的程式運行得更快,就請執行該操作,只有在沒有其他選擇的情況下才可以選擇并發,
但并發還是有它的價值所在的,隨著我們提高時鐘能力的耗盡,速度的提升將以更多處理器核心的形式而非更快的芯片,為了使程式運行得更快,你必須學會利用額外的處理器,另外,并發也能提高單處理器上運行程式的性能,前提是解決等待所帶來的收益要高于背景關系切換的開銷,
并發會帶來各種成本,包括復雜性成本,但可以被程式設計、資源平衡和用戶便利性方面的改進所抵消,通常,并發性使你能設計出更低耦合的代碼,但與其同時,你必須特別留意使用了并發操作的那一部分,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/243756.html
標籤:Java
上一篇:【Java流程控制】回圈跳轉陳述句continue、break、return的區別
下一篇:【NC】出補丁與打補丁
