在Java中同时运行线程时,ExecutorService的execute和thread.run有什么区别?

6

我刚接触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");              
}

我的问题是:
在并发编程中,触发线程的最佳实践是什么?
如果我使用第一种或第二种方法,将会得到什么结果(麻烦)?
注意:我在第一种情况下遇到了一个问题,程序在几天后就会卡死。那么,当我使用第一种方法时,这个问题是否相关/预期出现?
任何好的/有帮助的答案都将不胜感激 :) 谢谢


可能是线程池与线程生成的重复问题 - Narendra Pathai
@NarendraPathai 我的问题更具体,如果是这样,你怎么能将其标记为重复? - Kishore Kumar Korada
@NarendraPathai,我只是提到“挂起”这个词来澄清FirstScenario是否可能引起此类问题。为了让你理解,我并没有要求解决那个“挂起”的问题。谢谢你的参考。 - Kishore Kumar Korada
2个回答

4

你所提供的两种方案并没有太大的区别,除了在Scenario2中需要管理线程终止。对于每个传入请求,你总是创建一个新的线程。如果你想使用线程池,我的建议是不要为每个请求创建一个线程,而是为每个服务器创建一个线程池并重用线程。例如:

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.1秒到达的请求可以重用用于处理0秒时刻请求的线程)。三个线程管理十个请求。

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


如果我使用newCachedThreadPool()或newFixedThreadPool(int nThreads),因为我每隔四秒从成百上千的客户端获取数据,如果我不为每个客户端使用单个线程,我会遇到什么问题吗? - Kishore Kumar Korada
1
如果您正确地调整了池的大小,使用newCachedThreadPool()即可不必对线程数设置上限,就能避免问题。 - Giovanni

1

来自 Oracle 文档,来自 Executors

public static ExecutorService newCachedThreadPool()

创建一个线程池,按需创建新线程,但在可用时将重复使用先前构建的线程。这些池通常会提高执行许多短暂异步任务的程序的性能。
如果有现有的线程可用,则对execute的调用将重复使用先前构建的线程。如果没有现有线程可用,则将创建一个新线程并将其添加到池中。未使用60秒的线程将被终止并从缓存中删除。
因此,长时间闲置的池不会消耗任何资源。请注意,可以使用ThreadPoolExecutor构造函数创建具有类似属性但不同细节(例如超时参数)的池。
public static ExecutorService newFixedThreadPool(int nThreads)

创建一个线程池,该线程池重用一定数量的线程,这些线程在共享的无界队列上运行。在任何时刻,最多有nThreads个线程处于活动状态来处理任务。如果在所有线程都处于活动状态时提交了额外的任务,则它们将在队列中等待,直到有线程可用为止。
如果任何线程在关闭之前由于执行期间的故障而终止,则如果需要执行后续任务,将会有一个新线程取代它。线程池中的线程将一直存在,直到明确关闭为止。
@Giovanni表示,与newFixedThreadPool()不同,您不必为newCachedThreadPool提供线程数,其中您必须传递线程池中线程数的最大限制。
但是,在这两者之间,newFixedThreadPool()更受欢迎。newCachedThreadPool可能会导致泄漏,并且由于无界性质,您可能会达到可用线程的最大数量。有些人认为它是邪恶的。
请参阅相关SE问题: 为什么通过newCachedThreadPool创建的ExecutorService是邪恶的?

使用newFixedThreadPool()时,如何确定线程数? - JayC
从Runtime.getRuntime().availableProcessors()开始。 - Aditya W

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接