2012-10-17 67 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库的总体运行速度更快,重量更轻,开销更小。