一、引言
并發(fā)編程的目的是為了讓程序運(yùn)行的更快,但是并不是啟動(dòng)更多的線(xiàn)程就能讓程序最大限度的并發(fā)執(zhí)行。這其中存在著很多的挑戰(zhàn),比如上下文的切換問(wèn)題、死鎖、受限于軟硬件的限制。
二、上下文切換
- 即使是單核處理器也支持多線(xiàn)程執(zhí)行代碼,CPU通過(guò)給每個(gè)線(xiàn)程分配CPU的時(shí)間片來(lái)實(shí)現(xiàn)這個(gè)機(jī)制。
- 時(shí)間片是CPU分配給各個(gè)線(xiàn)程的時(shí)間,因?yàn)闀r(shí)間片非常短,所以CPU通過(guò)不斷的切換線(xiàn)程執(zhí)行,讓我們覺(jué)得是多個(gè)先線(xiàn)程是同時(shí)執(zhí)行的,時(shí)間片一般是幾十毫秒。
- CPU通過(guò)時(shí)間片分配算法來(lái)循環(huán)執(zhí)行任務(wù),當(dāng)前任務(wù)執(zhí)行一個(gè)時(shí)間片后會(huì)切換到下一個(gè)任務(wù)。但是,在切換前會(huì)保存上一個(gè)任務(wù)的狀態(tài),以便下次切換回這個(gè)任務(wù)時(shí),可以加載這個(gè)任務(wù)的狀態(tài)。所以任務(wù)從保存到再加載的過(guò)程就是一次上下文切換。
多線(xiàn)程一定快嗎?
當(dāng)并發(fā)執(zhí)行累加操作不超過(guò)百萬(wàn)次時(shí),速度會(huì)比串行執(zhí)行累加操作要慢,這是為什么呢?因?yàn)榫€(xiàn)程有創(chuàng)建和上下文切換的開(kāi)銷(xiāo)(說(shuō)明:上下文每1秒切換1000多次)。
如何減少上下文切換
- 無(wú)鎖并發(fā)編程:多線(xiàn)程競(jìng)爭(zhēng)鎖時(shí),會(huì)引起上下文切換,所以多線(xiàn)程處理數(shù)據(jù)時(shí),可以用一些辦法來(lái)避免使用鎖,如將數(shù)據(jù)的ID按照Hash算法取模分段,不同的線(xiàn)程處理不同段的數(shù)據(jù)。
- CAS算法:Java的Atomic包使用CAS算法來(lái)更新數(shù)據(jù),而不需要加鎖。
- 使用最少線(xiàn)程:避免創(chuàng)建不需要的線(xiàn)程。比如任務(wù)很少,但是創(chuàng)建了很多線(xiàn)程來(lái)處理,這樣造成大量線(xiàn)程都處于等待狀態(tài)。
- 使用協(xié)程:在單線(xiàn)程里實(shí)現(xiàn)多任務(wù)的調(diào)度,并在單線(xiàn)程里維持多個(gè)任務(wù)間的切換。
三、死鎖
鎖是個(gè)非常有用的工具,運(yùn)用場(chǎng)景也非常多。但是,它也給我們帶來(lái)了很大的困擾——死鎖。
避免死鎖的常見(jiàn)方法
- 避免一個(gè)線(xiàn)程同時(shí)獲取多個(gè)鎖。
- 避免一個(gè)線(xiàn)程在鎖內(nèi)同時(shí)占有多個(gè)資源,盡量保證每個(gè)鎖只占有一個(gè)資源。
- 嘗試使用定時(shí)鎖,使用lock.tryLock(timeout)來(lái)替代使用內(nèi)部鎖機(jī)制)。
- 對(duì)于數(shù)據(jù)庫(kù)鎖,加鎖和解鎖必須在一個(gè)數(shù)據(jù)庫(kù)連接里,否則會(huì)出現(xiàn)解鎖失敗的情況。
四、資源限制的挑戰(zhàn)
什么是資源限制
- 硬件資源限制:帶寬的上傳/下載速度、硬盤(pán)的讀寫(xiě)速度、CPU的處理速度......
- 軟件資源限制:數(shù)據(jù)庫(kù)的連接數(shù)、socket連接數(shù)......
資源限制引發(fā)的問(wèn)題
在并發(fā)編程中,將代碼執(zhí)行速度加快的原則是將代碼中串行執(zhí)行的部分變?yōu)椴l(fā)執(zhí)行,但是因?yàn)橘Y源限制,代碼仍然在串行執(zhí)行。如果是這樣,不僅不會(huì)加快執(zhí)行,反而會(huì)更慢,因?yàn)?strong>增加了上下文切換和資源調(diào)度的時(shí)間。
如何解決資源限制的問(wèn)題
- 硬件資源限制:可以考慮使用集群并行執(zhí)行程序,既然單機(jī)的資源有限,那么就讓程序在多機(jī)上執(zhí)行。
- 軟件資源限制:可以考慮使用資源池將資源復(fù)用。比如使用連接池將數(shù)據(jù)庫(kù)和Socket連接復(fù)用;在調(diào)用webservice接口獲取數(shù)據(jù)時(shí),只建立一個(gè)來(lái)連接。
在資源限制情況下如何進(jìn)行并發(fā)編程?
根據(jù)不同的資源限制調(diào)整程序的并發(fā)度。比如,文件下載依賴(lài)于——帶寬和硬盤(pán)讀寫(xiě)速度;數(shù)據(jù)庫(kù)操作時(shí),涉及數(shù)據(jù)庫(kù)連接數(shù),如果SQL語(yǔ)句執(zhí)行非???,而線(xiàn)程的數(shù)據(jù)比數(shù)據(jù)庫(kù)的連接數(shù)大很多,則某些線(xiàn)程會(huì)被阻塞,等待數(shù)據(jù)庫(kù)連接。
建議:對(duì)于Java開(kāi)發(fā)工程師而言,尤其初、中級(jí)程序員,應(yīng)該多使用JDK并發(fā)包提供的并發(fā)容器和工具類(lèi)來(lái)解決并發(fā)問(wèn)題。一方面可以了解大神們的解決方案,另一方面減少自己開(kāi)創(chuàng)的并發(fā)編程帶來(lái)的不必要的bug。