多線程使用的主要目的在于:
1、吞吐量:你做WEB,容器幫你做了多線程,但是他只能幫你做請求層面的。簡單的說,可能就是一個請求一個線程。或多個請求一個線程。如果是單線程,那同時只能處理一個用戶的請求。
2、伸縮性:也就是說,你可以通過增加CPU核數來提升性能。如果是單線程,那程序執行到死也就利用了單核,肯定沒辦法通過增加CPU核數來提升性能。鑒于你是做WEB的,第1點可能你幾乎不涉及。那這里我就講第二點吧。--舉個簡單的例子:假設有個請求,這個請求服務端的處理需要執行3個很緩慢的IO操作(比如數據庫查詢或文件查詢),那么正常的順序可能是(括號里面代表執行時間):
a、讀取文件1 (10ms)
b、處理1的數據(1ms)
c、讀取文件2 (10ms)
d、處理2的數據(1ms)
e、讀取文件3 (10ms)
f、處理3的數據(1ms)
g、整合1、2、3的數據結果 (1ms)
單線程總共就需要34ms。
那如果你在這個請求內,把ab、cd、ef分別分給3個線程去做,就只需要12ms了。
所以多線程不是沒怎么用,而是,你平常要善于發現一些可優化的點。然后評估方案是否應該使用。假設還是上面那個相同的問題:但是每個步驟的執行時間不一樣了。
a、讀取文件1 (1ms)
b、處理1的數據(1ms)
c、讀取文件2 (1ms)
d、處理2的數據(1ms)
e、讀取文件3 (28ms)
f、處理3的數據(1ms)
g、整合1、2、3的數據結果 (1ms)單線程總共就需要34ms。
如果還是按上面的劃分方案(上面方案和木桶原理一樣,耗時取決于最慢的那個線程的執行速度),在這個例子中是第三個線程,執行29ms。那么最后這個請求耗時是30ms。比起不用單線程,就節省了4ms。但是有可能線程調度切換也要花費個1、2ms。
因此,這個方案顯得優勢就不明顯了,還帶來程序復雜度提升。不太值得。那么現在優化的點,就不是第一個例子那樣的任務分割多線程完成。而是優化文件3的讀取速度。可能是采用緩存和減少一些重復讀取。首先,假設有一種情況,所有用戶都請求這個請求,那其實相當于所有用戶都需要讀取文件3。
那你想想,100個人進行了這個請求,相當于你花在讀取這個文件上的時間就是28×100=2800ms了。那么,如果你把文件緩存起來,那只要第一個用戶的請求讀取了,第二個用戶不需要讀取了,從內存取是很快速的,可能1ms都不到。偽代碼:
看起來好像還不錯,建立一個文件名和文件數據的映射。如果讀取一個map中已經存在的數據,那么就不不用讀取文件了。可是問題在于,Servlet是并發,上面會導致一個很嚴重的問題,死循環。因為,HashMap在并發修改的時候,可能是導致循環鏈表的構成!!!(具體你可以自行閱讀HashMap源碼)如果你沒接觸過多線程,可能到時候發現服務器沒請求也巨卡,也不知道什么情況!好的,那就用ConcurrentHashMap,正如他的名字一樣,他是一個線程安全的HashMap,這樣能輕松解決問題。
這樣真的解決問題了嗎,這樣雖然只要有用戶訪問過文件a,那另一個用戶想訪問文件a,也會從fileName2Data中拿數據,然后也不會引起死循環。可是,如果你覺得這樣就已經完了,那你把多線程也想的太簡單了,騷年!你會發現,1000個用戶首次訪問同一個文件的時候,居然讀取了1000次文件(這是最極端的,可能只有幾百)。What the fuckin hell!!!難道代碼錯了嗎,難道我就這樣過我的一生!好好分析下。Servlet是多線程的,那么
上面注釋的“偶然”,這是完全有可能的,因此,這樣做還是有問題。因此,可以自己簡單的封裝一個任務來處理。
以上所有代碼都是直接在bbs打出來的,不保證可以直接運行。
多線程最多的場景:web服務器本身;各種專用服務器(如游戲服務器);多線程的常見應用場景:
1、后臺任務,例如:定時向大量(100w以上)的用戶發送郵件;
2、異步處理,例如:發微博、記錄日志等;
3、分布式計算