在我以前的一篇《再有人問你Java內存模型是什麼,就把這篇文章發給他。》文章中,介紹了Java內存模型,經過這篇文章,你們應該都知道了Java內存模型的概念以及做用,這篇文章中談到,在Java併發編程中,一般會遇到三個問題,即原子性問題、一致性問題和有序性問題。算法
上面一篇文章簡單介紹了一下,因爲各類緣由會致使多線程場景下可能存在原子性、一致性和有序性問題。可是並無深刻,這篇文章就來在以前的基礎上,再來看一下,併發編程中,這些問題都是哪來的?編程
首先,咱們仍是從操做系統開始,先來了解一些基礎知識。緩存
不少人都知道,如今咱們用到操做系統,不管是Windows、Linux仍是MacOS等其實都是多用戶多任務分時操做系統。使用這些操做系統的「用戶」是能夠「同時」幹多件事的,這已是平常習慣了,並沒以爲有什麼特別。服務器
可是實際上,對於單CPU的計算機來講,在CPU中,同一時間是隻能幹一件事兒的。多線程
爲了看起來像是「同時幹多件事」,分時操做系統是把CPU的時間劃分紅長短基本相同的時間區間,即」時間片」,經過操做系統的管理,把這些時間片依次輪流地分配給各個「用戶」使用。併發
若是某個「用戶」在時間片結束以前,整個任務尚未完成,「用戶」就必須進入到就緒狀態,放棄CPU,等待下一輪循環。此時CPU又分配給另外一個「用戶」去使用。性能
CPU 就好像是一個電話亭,他能夠開放給全部用戶使用,可是他有規定,每一個用戶進入電話亭以後只能使用規定時長的時間。若是時間到了,用戶還沒打完電話,那就會被要求去從新排隊。優化
不一樣的操做系統,在選擇「用戶」分配時間片的調度算法是不同的,經常使用的有FCFS、輪轉、SPN、SRT、HRRN、反饋等,因爲不是本文重點,就不展開了。atom
這個電話亭能夠容許哪一個用戶進入打電話是有不一樣的策略的,不一樣的電話亭規定不一樣,有的電話亭採用排隊機制(FCFS)、有的優先分配給打電話時間最短的人(SPN)等操作系統
前面介紹CPU時間片的時候提到了CPU會根據不一樣的調度算法把時間片分配給「用戶」,這裏的「用戶」在之前指的是進程,隨着操做系統的不斷髮展,如今通常指線程。
在過去沒有線程的操做系統中,資源的分配和執行都是由進程完成的。隨着技術的發展,爲了減小因爲進程切換帶來的開銷,提高併發能力,操做系統中引入線程。把本來屬於進程的工做一分爲二,進程仍是負責資源的分配,而線程負責執行。
也就是說,進程是資源分配的基本單位,而線程是調度的基本單位。
瞭解了以上的和硬件及操做系統有關的基礎知識之後,咱們再來看下,在多線程場景中有哪些併發問題。
關於併發編程中的原子性、可見性和有序性問題我在《內存模型》一文介紹過。
文中提到:緩存一致性問題其實就是可見性問題。而處理器優化是能夠致使原子性問題的。指令重排即會致使有序性問題。有部分讀者對這部分不是很理解。因爲上一篇文章主要介紹內存模型,並無展開分析,只是給了個結論,這裏再針對這部分深刻分析一下。
因爲緩存一致性問題致使可見性問題,在《內存模型》中介紹的很清晰了,這裏就不贅述了,主要結合本文來分析下原子性問題和有序性問題。
咱們說原子性問題,其實指的是多線程場景中操做若是不能保證原子性,會致使處理結果和預期不一致。
前面咱們提到過,線程是CPU調度的基本單位。CPU有時間片的概念,會根據不一樣的調度算法進行線程調度。因此在多線程場景下,就會發生原子性問題。由於線程在執行一個讀改寫
操做時,在執行完讀改
以後,時間片耗完,就會被要求放棄CPU,並等待從新調度。這種狀況下,讀改寫
就不是一個原子操做。
就好像咱們去電話亭打電話,一共有三個步驟,查找電話,撥號,交流。因爲咱們在電話亭中能夠停留的時間有限,有可能剛剛找到電話號碼,時間到了,就被趕出來了。
在單線程中,一個讀改寫
就算不是原子操做也不要緊,由於只要這個線程再次被調度,這個操做老是能夠執行完的。可是在多線程場景中可能就有問題了。由於多個線程可能會對同一個共享資源進行操做。
好比經典的 i++
操做,對於一個簡單的i++
操做,一共有三個步驟:load
, add
,save
。共享變量就會被多個線程同時進行操做,這樣讀改寫操做就不是原子的,操做完以後共享變量的值會和指望的不一致,舉個例子:若是i=1,咱們進行兩次i++
操做,咱們指望的結果是3,可是有可能結果是2。
並且,咱們知道,除了引入了時間片之外,因爲處理器優化和指令重排等,CPU還可能對輸入代碼進行亂序執行,好比load
->add
->save
有可能被優化成load
->save
->add
。這就是有序性問題。
咱們打電話的時候,除了可能被中途趕出來之外,原本正常步驟是要查找電話、撥號、交流的。可是電話亭非要給咱們優化成查找電話、交流、撥號。這確定不是咱們想要的啊。
仍是剛剛的i++
操做,在知足了原子性的狀況下,若是沒有知足有序性,那麼獲得的結果可能也不是咱們想要的。
本文主要介紹了併發編程中會致使原子性和有序性問題的緣由,關於可見性請參考《內存模型》。關於這三種問題的解決方案在《內存模型》也有介紹,更多的能夠參考多線程相關書籍。Hollis後續也會出更多文章再深刻分析,敬請期待。
再有人問你Java內存模型是什麼,就把這篇文章發給他。 服務器性能指標(二)——CPU利用率分析及問題排查 聊聊併發(五)——原子操做的實現原理