***JAVA多線程的應用場景和應用目的舉例

多線程使用的主要目的在於:

一、吞吐量:你作WEB,容器幫你作了多線程,可是他只能幫你作請求層面的。簡單的說,可能就是一個請求一個線程。或多個請求一個線程。若是是單線程,那同時只能處理一個用戶的請求。

二、伸縮性:也就是說,你能夠經過增長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、整合一、二、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、整合一、二、3的數據結果 (1ms)
單線程總共就須要34ms。
若是仍是按上面的劃分方案(上面方案和木桶原理同樣,耗時取決於最慢的那個線程的執行速度),在這個例子中是第三個線程,執行29ms。那麼最後這個請求耗時是30ms。比起不用單線程,就節省了4ms。可是有可能線程調度切換也要花費個一、2ms。所以,這個方案顯得優點就不明顯了,還帶來程序複雜度提高。不太值得。

那麼如今優化的點,就不是第一個例子那樣的任務分割多線程完成。而是優化文件3的讀取速度。
多是採用緩存和減小一些重複讀取。
首先,假設有一種狀況,全部用戶都請求這個請求,那其實至關於全部用戶都須要讀取文件3。那你想一想,100我的進行了這個請求,至關於你花在讀取這個文件上的時間就是28×100=2800ms了。那麼,若是你把文件緩存起來,那隻要第一個用戶的請求讀取了,第二個用戶不須要讀取了,從內存取是很快速的,可能1ms都不到。

僞代碼:java

Java code
 
?
1
2
3
4
5
6
7
8
9
10
11
public  class  MyServlet  extends  Servlet{
     private  static  Map<String, String> fileName2Data =  new  HashMap<String, String>();
     private  void  processFile3(String fName){
         String data = fileName2Data.get(fName);
         if (data== null ){
             data = readFromFile(fName);     //耗時28ms
             fileName2Data.put(fName, data);
         }
         //process with data
     }
}


看起來好像還不錯,創建一個文件名和文件數據的映射。若是讀取一個map中已經存在的數據,那麼就不不用讀取文件了。
但是問題在於,Servlet是併發,上面會致使一個很嚴重的問題,死循環。由於,HashMap在併發修改的時候,多是致使循環鏈表的構成!!!(具體你能夠自行閱讀HashMap源碼)若是你沒接觸過多線程,可能到時候發現服務器沒請求也巨卡,也不知道什麼狀況!
好的,那就用ConcurrentHashMap,正如他的名字同樣,他是一個線程安全的HashMap,這樣能輕鬆解決問題。web

Java code
 
?
1
2
3
4
5
6
7
8
9
10
11
public  class  MyServlet  extends  Servlet{
     private  static  ConcurrentHashMap<String, String> fileName2Data =  new  ConcurrentHashMap<String, String>();
     private  void  processFile3(String fName){
         String data = fileName2Data.get(fName);
         if (data== null ){
             data = readFromFile(fName);     //耗時28ms
             fileName2Data.put(fName, data);
         }
         //process with data
     }
}



這樣真的解決問題了嗎,這樣雖然只要有用戶訪問過文件a,那另外一個用戶想訪問文件a,也會從fileName2Data中拿數據,而後也不會引發死循環。

但是,若是你以爲這樣就已經完了,那你把多線程也想的太簡單了,騷年!
你會發現,1000個用戶首次訪問同一個文件的時候,竟然讀取了1000次文件(這是最極端的,可能只有幾百)。What the fuckin hell!!!

難道代碼錯了嗎,難道我就這樣過個人一輩子!

好好分析下。Servlet是多線程的,那麼

數據庫

Java code
 
?
1
2
3
4
5
6
7
8
9
10
11
12
public  class  MyServlet  extends  Servlet{
     private  static  ConcurrentHashMap<String, String> fileName2Data =  new  ConcurrentHashMap<String, String>();
     private  void  processFile3(String fName){
         String data = fileName2Data.get(fName);
         //「偶然」-- 1000個線程同時到這裏,同時發現data爲null
         if (data== null ){
             data = readFromFile(fName);     //耗時28ms
             fileName2Data.put(fName, data);
         }
         //process with data
     }
}


上面註釋的「偶然」,這是徹底有可能的,所以,這樣作仍是有問題。

所以,能夠本身簡單的封裝一個任務來處理。緩存

Java code
 
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public  class  MyServlet  extends  Servlet{
     private  static  ConcurrentHashMap<String, FutureTask> fileName2Data =  new  ConcurrentHashMap<String, FutureTask>();
     private  static  ExecutorService exec = Executors.newCacheThreadPool();
     private  void  processFile3(String fName){
         FutureTask data = fileName2Data.get(fName);
         //「偶然」-- 1000個線程同時到這裏,同時發現data爲null
         if (data== null ){
             data = newFutureTask(fName);
             FutureTask old = fileName2Data.putIfAbsent(fName, data);
             if (old== null ){
                 data = old;
             } else {
                 exec.execute(data);
             }
         }
         String d = data.get();
         //process with data
     }
     
     private  FutureTask newFutureTask( final  String file){
         return   new  FutureTask( new  Callable<String>(){
             public  String call(){
                 return  readFromFile(file);
             }
 
             private  String readFromFile(String file){ return  "" ;}
         }
     }
}



以上全部代碼都是直接在bbs打出來的,不保證能夠直接運行。安全

 

多線程最多的場景:web服務器自己;各類專用服務器(如遊戲服務器);
多線程的常見應用場景:
一、後臺任務,例如:定時向大量(100w以上)的用戶發送郵件;
二、異步處理,例如:發微博、記錄日誌等;
三、分佈式計算服務器

相關文章
相關標籤/搜索