07.25 Java編程——Java的併發編程中的多線程問題到底是啥?

Java編程——Java的併發編程中的多線程問題到底是啥?

很多人可能已經很熟悉操作系統中的多任務:就是同一時刻運行多個程序的能力。

多線程程序在較低層次上擴展了多任務的概念:一個程序同時執行多個任務。通常每一個任務稱為一個線程,它是線程控制的簡稱。可以同時運行一個以上線程的程序成為多線程程序。首先,我們還是從操作系統開始,先來了解一些基礎知識。

CPU時間片

很多人都知道,現在我們用到操作系統,無論是Windows、Linux還是MacOS等其實都是多用戶多任務分時操作系統。使用這些操作系統的“用戶”是可以“同時”幹多件事的,這已經是日常習慣了,並沒覺得有什麼特別。

但是實際上,對於單CPU的計算機來說,在CPU中,同一時間是隻能幹一件事兒的。

為了看起來像是“同時幹多件事”,分時操作系統是把CPU的時間劃分成長短基本相同的時間區間,即”時間片”,通過操作系統的管理,把這些時間片依次輪流地分配給各個“用戶”使用。

如果某個“用戶”在時間片結束之前,整個任務還沒有完成,“用戶”就必須進入到就緒狀態,放棄CPU,等待下一輪循環。此時CPU又分配給另一個“用戶”去使用。

CPU 就好像是一個電話亭,他可以開放給所有用戶使用,但是他有規定,每個用戶進入電話亭之後只能使用規定時長的時間。如果時間到了,用戶還沒打完電話,那就會被要求去重新排隊。

不同的操作系統,在選擇“用戶”分配時間片的調度算法是不一樣的,常用的有FCFS、輪轉、SPN、SRT、HRRN、反饋等,由於不是本文重點,就不展開了。

這個電話亭可以允許哪個用戶進入打電話是有不同的策略的,不同的電話亭規定不同,有的電話亭採用排隊機制(FCFS)、有的優先分配給打電話時間最短的人(SPN)等

進程與線程

前面介紹CPU時間片的時候提到了CPU會根據不同的調度算法把時間片分配給“用戶”,這裡的“用戶”在以前指的是進程,隨著操作系統的不斷髮展,現在一般指線程。

在過去沒有線程的操作系統中,資源的分配和執行都是由進程完成的。隨著技術的發展,為了減少由於進程切換帶來的開銷,提升併發能力,操作系統中引入線程。把原本屬於進程的工作一分為二,進程還是負責資源的分配,而線程負責執行。

也就是說,進程是資源分配的基本單位,而線程是調度的基本單位。

多線程中的併發問題

瞭解了以上的和硬件及操作系統有關的基礎知識以後,我們再來看下,在多線程場景中有哪些併發問題。

關於併發編程中的原子性、可見性和有序性問題我在《內存模型》一文介紹過。

文中提到:緩存一致性問題其實就是可見性問題。而處理器優化是可以導致原子性問題的。指令重排即會導致有序性問題。有部分讀者對這部分不是很理解。由於上一篇文章主要介紹內存模型,並沒有展開分析,只是給了個結論,這裡再針對這部分深入分析一下。

由於緩存一致性問題導致可見性問題,在《內存模型》中介紹的很清晰了,這裡就不贅述了,主要結合本文來分析下原子性問題和有序性問題。

原子性問題

我們說原子性問題,其實指的是多線程場景中操作如果不能保證原子性,會導致處理結果和預期不一致。

前面我們提到過,線程是CPU調度的基本單位。CPU有時間片的概念,會根據不同的調度算法進行線程調度。所以在多線程場景下,就會發生原子性問題。因為線程在執行一個讀改寫操作時,在執行完讀改之後,時間片耗完,就會被要求放棄CPU,並等待重新調度。這種情況下,讀改寫就不是一個原子操作。

就好像我們去電話亭打電話,一共有三個步驟,查找電話,撥號,交流。由於我們在電話亭中可以停留的時間有限,有可能剛剛找到電話號碼,時間到了,就被趕出來了。

在單線程中,一個讀改寫就算不是原子操作也沒關係,因為只要這個線程再次被調度,這個操作總是可以執行完的。但是在多線程場景中可能就有問題了。因為多個線程可能會對同一個共享資源進行操作。

比如經典的 i++ 操作,對於一個簡單的i++操作,一共有三個步驟:load , add ,save 。共享變量就會被多個線程同時進行操作,這樣讀改寫操作就不是原子的,操作完之後共享變量的值會和期望的不一致,舉個例子:如果i=1,我們進行兩次i++操作,我們期望的結果是3,但是有可能結果是2。

Java編程——Java的併發編程中的多線程問題到底是啥?

有序性問題

而且,我們知道,除了引入了時間片以外,由於處理器優化和指令重排等,CPU還可能對輸入代碼進行亂序執行,比如load->add->save 有可能被優化成load->save->add 。這就是有序性問題。

我們打電話的時候,除了可能被中途趕出來以外,本來正常步驟是要查找電話、撥號、交流的。但是電話亭非要給我們優化成查找電話、交流、撥號。這肯定不是我們想要的啊。

還是剛剛的i++操作,在滿足了原子性的情況下,如果沒有滿足有序性,那麼得到的結果可能也不是我們想要的。

Java編程——Java的併發編程中的多線程問題到底是啥?

Java編程——Java的併發編程中的多線程問題到底是啥?


分享到:


相關文章: