在過去單 CPU 時(shí)代,單任務(wù)在一個(gè)時(shí)間點(diǎn)只能執(zhí)行單一程序。之后發(fā)展到多任務(wù)階段,計(jì)算機(jī)能在同一時(shí)間點(diǎn)并行執(zhí)行多任務(wù)或多進(jìn)程。雖然并不是真正意義上的“同一時(shí)間點(diǎn)”,而是多個(gè)任務(wù)或進(jìn)程共享一個(gè) CPU,并交由操作系統(tǒng)來完成多任務(wù)間對 CPU 的運(yùn)行切換,以使得每個(gè)任務(wù)都有機(jī)會(huì)獲得一定的時(shí)間片運(yùn)行。
隨著多任務(wù)對軟件開發(fā)者帶來的新挑戰(zhàn),程序不在能假設(shè)獨(dú)占所有的 CPU 時(shí)間、所有的內(nèi)存和其他計(jì)算機(jī)資源。一個(gè)好的程序榜樣是在其不再使用這些資源時(shí)對其進(jìn)行釋放,以使得其他程序能有機(jī)會(huì)使用這些資源。
再后來發(fā)展到多線程技術(shù),使得在一個(gè)程序內(nèi)部能擁有多個(gè)線程并行執(zhí)行。一個(gè)線程的執(zhí)行可以被認(rèn)為是一個(gè) CPU 在執(zhí)行該程序。當(dāng)一個(gè)程序運(yùn)行在多線程下,就好像有多個(gè) CPU 在同時(shí)執(zhí)行該程序。
多線程比多任務(wù)更加有挑戰(zhàn)。多線程是在同一個(gè)程序內(nèi)部并行執(zhí)行,因此會(huì)對相同的內(nèi)存空間進(jìn)行并發(fā)讀寫操作。這可能是在單線程程序中從來不會(huì)遇到的問題。其中的一些錯(cuò)誤也未必會(huì)在單 CPU 機(jī)器上出現(xiàn),因?yàn)閮蓚€(gè)線程從來不會(huì)得到真正的并行執(zhí)行。然而,更現(xiàn)代的計(jì)算機(jī)伴隨著多核 CPU 的出現(xiàn),也就意味著不同的線程能被不同的 CPU 核得到真正意義的并行執(zhí)行。
如果一個(gè)線程在讀一個(gè)內(nèi)存時(shí),另一個(gè)線程正向該內(nèi)存進(jìn)行寫操作,那進(jìn)行讀操作的那個(gè)線程將獲得什么結(jié)果呢?是寫操作之前舊的值?還是寫操作成功之后的新值?或是一半新一半舊的值?或者,如果是兩個(gè)線程同時(shí)寫同一個(gè)內(nèi)存,在操作完成后將會(huì)是什么結(jié)果呢?是第一個(gè)線程寫入的值?還是第二個(gè)線程寫入的值?還是兩個(gè)線程寫入的一個(gè)混合值?因此如沒有合適的預(yù)防措施,任何結(jié)果都是可能的。而且這種行為的發(fā)生甚至不能預(yù)測,所以結(jié)果也是不確定性的。
Java 是最先支持多線程的開發(fā)的語言之一,Java 從一開始就支持了多線程能力,因此 Java 開發(fā)者能常遇到上面描述的問題場景。這也是我想為 Java 并發(fā)技術(shù)而寫這篇系列的原因。作為對自己的筆記,和對其他 Java 開發(fā)的追隨者都可獲益的。
該系列主要關(guān)注 Java 多線程,但有些在多線程中出現(xiàn)的問題會(huì)和多任務(wù)以及分布式系統(tǒng)中出現(xiàn)的存在類似,因此該系列會(huì)將多任務(wù)和分布式系統(tǒng)方面作為參考,所以叫法上稱為“并發(fā)性”,而不是“多線程”。