在Java中并行运行线程的ExecutorService执行和thread.run有什么区别?

时间:2015-06-20 11:37:02

标签: java multithreading concurrency executorservice

我是java中这种并发编程的新手,并提出了以下情况,我在这些情况下会遇到混淆。

场景1:在下面的代码中,我试图通过在GPSService类上调用 .start()来运行线程,这是一个Runnable实现。

int clientNumber = 0;
ServerSocket listener = new ServerSocket(port);

while (true) {
            new GPSService(listener.accept(), clientNumber++, serverUrl).start();
} 

场景2:在以下代码中,我尝试使用ExecutorService类来运行线程,如图所示

int clientNumber = 0;
ServerSocket listener = new ServerSocket(port);
while(true) {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        executor.execute(new GPSService(listener.accept(), client++, serverUrl));

        executor.shutdown();
        while (!executor.awaitTermination(1, TimeUnit.SECONDS)) {
            // Threads are still running
            System.out.println("Thread is still running");
        }
        // All threads are completed
        System.out.println("\nThread completed it's execution and terminated successfully\n");              
}

我的问题是 在并发编程中调用线程的最佳实践是什么? 当我使用第一个或第二个时,我会得到什么结果(麻烦)?
注意:我已经面临第一个方案的问题,即每隔几天该程序就会被挂起。那么,当我使用第一种方法时,该问题是否与预期有关 任何好的/有用的答案将不胜感激:)谢谢

2 个答案:

答案 0 :(得分:3)

除了在Scenario2中管理线程终止之外,您发布的两个场景没有太大差异;您总是为每个传入请求创建一个新线程。如果你想使用ThreadPool,我的建议不是为每个请求创建一个,而是为每个服务器创建一个并重用线程。类似的东西:

public class YourClass {

//in init method or constructor 
ExecutorService executor = Executors....;// choose from newCachedThreadPool() or newFixedThreadPool(int nThreads) or some custom option


int clientNumber = 0;
ServerSocket listener = new ServerSocket(port);
while(true) {

    executor.execute(new GPSService(listener.accept(), client++, serverUrl));

}

这将允许您使用线程池并控制用于服务器的线程数。如果你想使用Executor,这是首选方式。

使用服务器池,您需要确定池中有多少个线程;您有不同的选择,但您可以启动或使用固定数量或线程或尝试使用非繁忙线程的池,如果所有线程都忙,则会创建一个新线程(newCachedThreadPool())。要分配的线程数取决于许多因素:并发请求的数量和持续时间。服务器端代码花费的时间越多,您需要的额外线程就越多。如果您的服务器端代码非常快,那么池很可能会回收已经分配的线程(因为请求并非完全在同一时刻完成)。

例如,假设您在一秒钟内有10个请求,每个请求持续0.2秒;如果请求到达0,0.1,0.2,0.3,0.4,0.5,......的一部分(例如23/06/2015 7:16:00:00,23 / 06/2015 7:16:00: 01,23 / 06/2015 7:16:00:02)你只需要三个线程,因为0.3的请求可以由服务第一个请求的线程(0的一个)执行,依此类推(请求)在0.4时,可以重用用于0.1)的请求的线程。由三个线程管理的十个请求。

我建议你(如果你还没有)在实践中阅读 Java Concurrency (任务执行是第6章);这是一本关于如何在Java中构建并发应用程序的优秀书籍。

答案 1 :(得分:1)

来自Executors

的oracle documentation
public static ExecutorService newCachedThreadPool()
  

创建一个根据需要创建新线程的线程池,但在它们可用时将重用以前构造的线程。这些池通常会提高执行许多短期异步任务的程序的性能。

     

执行调用将重用以前构造的线程(如果可用)。如果没有可用的现有线程,则将创建一个新线程并将其添加到池中。未使用60秒的线程将终止并从缓存中删除。

     

因此,长时间闲置的池不会消耗任何资源。请注意,可以使用ThreadPoolExecutor构造函数创建具有相似属性但不同详细信息的池(例如,超时参数)。

public static ExecutorService newFixedThreadPool(int nThreads)
  

创建一个线程池,该线程池重用在共享无界队列中运行的固定数量的线程。在任何时候,最多nThreads线程将是活动的处理任务。如果在所有线程都处于活动状态时提交了其他任务,则它们将在队列中等待,直到线程可用。

     

如果任何线程因关闭前执行期间的故障而终止,则在需要执行后续任务时,新的线程将取代它。池中的线程将一直存在,直到明确关闭为止。

@Giovanni 表示您不必提供newCachedThreadPool的线程数,而不是newFixedThreadPool(),其中您必须传递线程数的最大上限在ThreadPool中。

但在这两者之间,newFixedThreadPool()是首选。 newCachedThread Pool可能会导致泄漏,并且由于无限制性质,您可能会达到最大可用线程数。有些人认为这是一种邪恶。

看看相关的SE问题:

Why is an ExecutorService created via newCachedThreadPool evil?