我是Java中并发编程的新手,并提出了以下场景,在这些场景中,我感到困惑何时使用哪个。
场景 1:在下面的代码中,我试图通过在GPSService类上调用.start((来运行线程,这是一个可运行的实现。
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 successfullyn");
}
我的问题是
在并发编程中调用线程的最佳实践是什么?
当我使用第一个或第二个时,我最终会得到什么结果(麻烦(?
注意:我一直面临着第一种情况的问题,即程序每隔几天就会挂起。那么,当我使用第一种方法时,这个问题是否相关/预期。?
任何好的/有用的答案将不胜感激:)谢谢
您发布的两个方案没有太大区别,除了在方案 2 中管理线程终止; 您始终为每个传入的请求创建一个新线程。如果你想使用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));
}
这将允许您使用线程池并控制要用于服务器的线程数。如果您想使用遗嘱执行器,这是首选方法。
对于服务器池,您需要决定池中有多少线程;您有不同的选择,但您可以启动或具有固定数量的线程或尝试使用非繁忙线程的池,如果所有线程都繁忙,它会创建一个新线程(newCachedThreadPool()
(。要分配的线程数取决于许多因素:并发请求数及其持续时间。服务器端代码花费的时间越多,对额外线程的需求就越多。如果您的服务器端代码非常快,则池很有可能可以回收已分配的线程(因为请求不会在同一时刻全部出现(。
例如,假设您在一秒钟内有 10 个请求,每个请求持续 0.2 秒;如果请求到达秒的 0、0.1、0.2、0.3、0.4、0.5、..part(例如 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并发实践(任务执行是第6章(;这是一本关于如何在Java中构建并发应用程序的好书。
来自 oracle 文档 来自 Executors
public static ExecutorService newCachedThreadPool()
创建一个线程池,该线程池根据需要创建新线程,但在以前构造的线程可用时将重用它们。这些池通常会提高执行许多短期异步任务的程序的性能。
要执行的调用将重用以前构造的线程(如果可用(。如果没有现有线程可用,则将创建一个新线程并将其添加到池中。六十秒未使用的线程将终止并从缓存中删除。
因此,保持空闲足够长时间的池不会消耗任何资源。请注意,可以使用 ThreadPoolExecutor 构造函数创建具有相似属性但不同详细信息(例如超时参数(的池。
public static ExecutorService newFixedThreadPool(int nThreads)
创建一个线程池,该线程池重用在共享的无限队列中运行的固定数量的线程。在任何时候,大多数 nThreads 线程都将是活动的处理任务。如果在所有线程都处于活动状态时提交其他任务,它们将在队列中等待,直到线程可用。
如果任何线程在关闭之前由于执行期间失败而终止,如果需要执行后续任务,将有一个新线程来取代它。池中的线程将一直存在,直到显式关闭。
@Giovanni 是说您不必提供线程数newCachedThreadPool
不像 newFixedThreadPool
((,您必须传递 ThreadPool 中线程数的最大上限。
但在这两者之间,newFixedThreadPool()
是首选。 newCachedThread Pool
可能会导致泄漏,并且由于无限的性质,您可能会达到最大可用线程数。有些人认为这是一种邪恶。
看看相关的SE问题:
为什么通过newCachedThreadPool邪恶创建ExecutorService?