2012-10-17 64 views
6

所以我有一個程序,我需要發送很多(如10,000+)GET請求到一個URL,我需要它儘可能快。當我第一次創建程序時,我只是把連接放到for循環中,但是它非常慢,因爲在繼續之前它必須等待每個連接完成。我想讓它更快,所以我嘗試使用線程,它使它更快,但我仍然不滿意。瞭解線程+異步

我猜這是正確的方法,並使其真正快速使用異步連接並連接到所有的URL。這是正確的方法嗎?

此外,我一直在嘗試理解線程和它們是如何工作的,但我似乎無法得到它。我所在的電腦有一個英特爾酷睿i7-3610QM四核處理器。根據英特爾網站對這款處理器的規格說明,它有8個線程。這是否意味着我可以在Java應用程序中創建8個線程,並且它們都將同時運行?如果超過8,那麼速度不會提高?

該數字代表「性能」選項卡下任務管理器中「線程」旁邊的數字是什麼?目前,我的任務管理器顯示「線程」超過1,000。爲什麼這個數字,如果這是我所有的處理器支持,它甚至會超過8? 我還注意到,當我用500個線程作爲測試來測試我的程序時,任務管理器中的數字增加了500,但它具有相同的速度,如果我將它設置爲使用8個線程來代替。所以如果數量是根據我在Java應用程序中使用的線程數量增加的,那麼爲什麼速度是相同的呢?

此外,我已經嘗試使用Java中的線程做一個小測試,但輸出對我來說沒有意義。 這裏是我的測試類:

import java.text.SimpleDateFormat; 
import java.util.Date; 

public class Test { 

    private static int numThreads = 3; 
    private static int numLoops = 100000; 
    private static SimpleDateFormat dateFormat = new SimpleDateFormat("[hh:mm:ss] "); 

    public static void main(String[] args) throws Exception { 

     for (int i=1; i<=numThreads; i++) { 
      final int threadNum = i; 
      new Thread(new Runnable() { 
       public void run() { 
        System.out.println(dateFormat.format(new Date()) + "Start of thread: " + threadNum); 
        for (int i=0; i<numLoops; i++) 
         for (int j=0; j<numLoops; j++); 
        System.out.println(dateFormat.format(new Date()) + "End of thread: " + threadNum); 
      } 
      }).start(); 
      Thread.sleep(2000); 
     } 

    } 
} 

這將產生一個輸出,如:

[09:48:51] Start of thread: 1 
[09:48:53] Start of thread: 2 
[09:48:55] Start of thread: 3 
[09:48:55] End of thread: 3 
[09:48:56] End of thread: 1 
[09:48:58] End of thread: 2 

爲什麼第三個線程開始和結束的時候了,而第一和第二採取每次5秒了嗎?如果我添加更多的3個線程,同樣的事情發生在2以上的所有線程。

對不起,如果這是一個長時間的閱讀,我有很多問題。 在此先感謝。

回答

9

你的處理器有8個內核,而不是線程。這實際上意味着在任何特定時刻只能運行8件事。這並不意味着你僅限於8個線程。

當一個線程正在同步打開一個到URL的連接時,它經常會在等待遠程服務器返回時進入休眠狀態。當該線程正在休眠時,其他線程可以正在工作。如果你有500個線程,並且所有的500個都在睡覺,那麼你沒有使用任何CPU的核心。另一方面,如果你有500個線程,並且所有500個線程都想要做某件事情,那麼它們不能一次全部運行。爲了處理這種情況,有一個特殊的工具。處理器(或更可能是操作系統或兩者的某種組合)具有調度器,該調度器確定哪些線程在任何給定時間在處理器上主動運行。有很多不同的規則,有時是隨機的活動來控制這些調度程序的工作方式。這可以解釋爲什麼在上面的例子中,線程3總是首先完成。也許調度器更喜歡第3個線程,因爲它是主線程調度的最新線程,有時不可能預測行爲。

現在回答你關於性能的問題。如果打開一個連接永遠不會涉及到睡眠,那麼如果你正在同步或異步處理事物並不重要,你將無法在8個線程之上獲得任何性能增益。實際上,開通連接所花費的時間很多都是在睡覺。異步和同步之間的區別是如何處理睡眠時間。理論上你應該能夠在兩者之間獲得幾乎相同的性能。

使用多線程模型,您只需創建比核心更多的線程。當線程進入休眠狀態時,讓其他線程工作。這有時更容易處理,因爲您不必在線程之間編寫任何調度或交互。

對於異步模型,您只能爲每個核心創建一個線程。如果該線程需要休眠,那麼它不會休眠,但實際上必須有代碼來處理切換到下一個連接。例如,假設有三個步驟中打開一個連接(A,B,C):

while (!connectionsList.isEmpty()) { 
    for(Connection connection : connectionsList) { 

    if connection.getState() == READY_FOR_A { 
     connection.stepA(); 
     //this method should return immediately and the connection 
     //should go into the waiting state for some time before going 
     //into the READY_FOR_B state 
    } 
    if connection.getState() == READY_FOR_B { 
     connection.stepB(); 
     //same immediate return behavior as above 
    } 
    if connection.getState() == READY_FOR_C { 
     connection.stepC(); 
     //same immediate return behavior as above 
    } 
    if connection.getState() == WAITING { 
     //Do nothing, skip over 
    } 
    if connection.getState() == FINISHED { 
     connectionsList.remove(connection); 
    } 
    } 
} 

注意,在任何時候確實線程睡眠所以在具有多個線程比你有芯沒有任何意義。最終,無論是採用同步方法還是採用異步方法,都是個人喜好的問題。只有在極端情況下,兩者之間纔會出現性能差異,您需要花費很長時間進行性能分析才能確定應用程序的瓶頸。

這聽起來像是你創造了很多線程並沒有獲得任何性能增益。這可能有多種原因。

  • 有可能您建立的連接實際上並沒有睡覺,在這種情況下,我不希望看到過去8個線程的性能增益。我認爲這不太可能。
  • 所有線程都可能使用一些公共共享資源。在這種情況下,其他線程無法工作,因爲睡眠線程具有共享資源。是否有任何線程共享的對象?這個對象是否有任何同步的方法?
  • 您可能有自己的同步。這可能會造成上述問題。
  • 有可能每個線程必須執行某種設置/分配工作,而這些工作正在通過使用多個線程擊敗您正在獲得的好處。

如果我是你,我會使用像JVisualVM這樣的工具來在使用少量線程(20)運行時分析你的應用程序。 JVisualVM有一個很好的彩色線程圖,它將顯示線程何時運行,阻塞或睡眠。這將幫助您理解線程/核心關係,因爲您應該看到正在運行的線程數量少於您擁有的核心數量。此外,如果看到大量被阻塞的線程,那麼這可能會導致您遇到瓶頸(如果看到大量被阻塞的線程,則使用JVisualVM在該時間點創建線程轉儲並查看線程被阻塞的線程)。

+0

感謝您的回覆。 編輯... – user1203585

+0

啊,我不能編輯評論真的... 5分鐘的限制...... 「是否有所有線程共享的任何對象?這是否對象有任何synchronized方法?」 我所有的線程都在做同樣的事情: 它實例化一個URL對象,並打開與代理的連接。它設置URLConnection連接和讀取超時。然後它使用BufferedReader和InputStreamReader從URLConnection讀取。最後,它將一個單詞寫入一個文本文件。 這就是每個線程正在做和運行這些線程500似乎並沒有加快步伐:/ – user1203585

+1

我做了一些周圍挖掘。我懷疑Java的底層連接池的大小是有限的。有一個名爲http.maxConnections [見這裏]一個網絡屬性(http://docs.oracle.com/javase/1.4.2/docs/guide/net/properties.html)。默認值爲5。這意味着你有它們都使用相同的5個底層套接字(共享資源),並且您之後打開將阻止任何連接超過5個連接打開之後。再次,您可以使用JVisualVM來確認這一點。 – Pace

1

一些概念:

您可以在系統中的多個線程,但只有一些(最多8個在你的情況下),在任何時間點在CPU上會被「預定」。所以,你不能獲得比並行運行的8個線程更多的性能。事實上,由於創建,銷燬和管理線程所涉及的工作,在增加線程數時性能可能會下降。

線程可以處於不同的狀態:http://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Thread.State.html 在這些狀態之外,RUNNABLE線程可以獲得一小部分CPU時間。操作系統決定爲線程分配CPU時間。在一個擁有1000個線程的常規系統中,當某個線程獲得CPU時間和在CPU上的時間時,它可能是完全不可預知的。

關於你解決這個問題:

你似乎想通了正確的解決方案 - 使並行異步網絡請求。但是,實際上,啓動10000多個線程和許多網絡連接同時可能會對系統資源造成壓力,並且可能無法正常工作。這post有許多關於使用Java的異步I/O的建議。 (提示:不要只看公認的答案)

0

此解決方案更具體到試圖儘可能快地完成10k請求的一般問題。我建議你放棄Java HTTP庫,並改用Apache的HttpClient。他們有幾個suggestions最大限度地提高性能,這可能是有用的。我聽說Apache HttpClient庫的總體運行速度更快,重量更輕,開銷更小。