了解线程+异步

时间:2012-10-17 02:57:14

标签: java multithreading asynchronous

所以我有一个程序,我需要向URL发送很多(如10,000+)GET请求,我需要它尽可能快。当我第一次创建程序时,我只是将连接放入for循环中,但它确实很慢,因为它必须等待每个连接完成才能继续。我想让它更快,所以我尝试使用线程,它使它更快但我仍然不满意。

我猜测正确的方法是使用异步连接并连接到所有URL。这是正确的做法吗?

另外,我一直在努力了解线程以及它们是如何工作的,但我似乎无法理解它。我所使用的计算机配有Intel Core i7-3610QM四核处理器。根据英特尔网站上有关该处理器的规格,它有8个线程。这是否意味着我可以在Java应用程序中创建8个线程,它们将同时运行?任何超过8并且没有速度增加?

“性能”标签下任务管理器中“线程”旁边的数字究竟代表什么?目前,我的任务管理器显示“线程”超过1,000。为什么这是这个数字,如果我的所有处理器都支持,它怎么能超过8呢? 我还注意到,当我尝试使用500个线程作为测试程序时,任务管理器中的数字增加了500但它的速度与我将其设置为使用8个线程相同。因此,如果数量根据我在Java应用程序中使用的线程数增加,那为什么速度相同呢?

另外,我尝试用Java中的线程做一个小测试,但输出对我没有意义。 这是我的Test类:

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以上的所有线程都会发生同样的事情。

对不起,如果这是一个长篇阅读,我有很多问题。 提前谢谢。

3 个答案:

答案 0 :(得分: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在该时间点创建线程转储并查看线程被阻止的内容)。

答案 1 :(得分: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有很多建议。 (提示:不要只看待接受的答案)

答案 2 :(得分:0)

此解决方案更具体地针对尽可能快地发出10k请求的一般问题。我建议您放弃Java HTTP库并使用Apache的HttpClient代替。他们有几个suggestions用于最大化性能,这可能是有用的。我听说Apache HttpClient库通常速度更快,重量更轻,开销也更少。