简短问题:在Tomcat 6应用程序中 - 如何运行(单独的)线程池?
运行线程池的最佳解决方案是什么?
长问题:
我在这里有一个简单的需要;
轮询数据库中的某些数据,同时允许Web客户端等待答复(长轮询连接)
当数据在数据库中可用时,我会向相关客户发送回复。
这么说,我更喜欢目前不要潜入任何框架(quartz scheduler
可能?)。
因此,正如我总结的那样,我需要一个线程池来在后台完成这项工作。
因此,如果我要使用Thread
(实际上是Runnable
),哪个类可以将其全部组织起来?是否有某种{{1}解决方案?有什么建议吗?
答案 0 :(得分:17)
回答你的简短问题:
在JVM中,线程池被抽象在java.util.concurrent.ExecutorService
接口之后。这个接口有不同的实现,但在大多数情况下,这个接口的方法就足够了。
要创建特定的线程池,请查看java.util.concurrent.Executors
类:
http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/Executors.html
其中包含用于创建ExecutorService
接口的不同实现的静态工厂方法。您可能对newFixedThreadPool(int threadsNumber)
和newCachedThreadPool
方法感兴趣。
有关JVM中Executors
的更多常规信息,您可能需要阅读以下Oracle教程:http://docs.oracle.com/javase/tutorial/essential/concurrency/executors.html
因此,要在Tomcat下使用线程池(ExecutorService
),您应该执行以下操作:
0.1。如果尚未完成,请在web.xml
接口的javax.servlet.ServletContextListener
实例中创建并注册(这将作为您的Web应用程序的入口点)。
0.2。在contextInitialized(ServletContextEvent)
方法中,您创建ExecutorService
(线程池)的实例并将其存储在ServletContext
属性映射中,以便可以从webapp中的任何位置访问它,例如:
// following method is invoked one time, when you web application starts (is deployed)
@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
// ...
final int numberOfThreads = ...;
final ExecutorService threadPool = Executors.newFixedThreadPool(numberOfThreads); // starts thread pool
final ServletContext servletContext = servletContextEvent.getServletContext();
servletContext.setAttribute("threadPoolAlias", threadPool);
// ...
}
// following method is invoked one time when your web application stops (is undeployed)
public void contextDestroyed(ServletContextEvent servletContextEvent) {
// following code is just to free resources occupied by thread pool when web application is undeployed
final ExecutorService threadPool = (ExecutorService) servletContextEvent.getServletContext().getAttribute("threadPoolAlias");
threadPool.shutdown();
}
0.3。在Servlet.service
方法的某个位置或您的webapp中的任何位置(您应该能够从webapp获得对ServletContext
的引用):
Callable<ResultOfMyTask> callable = new Callable<ResultOfMyTask>() {
public ResultOfMyTask call() {
// here goes your task code which is to be invoked by thread pool
}
};
final ServletContext servletContext = ...;
final ExecutorService threadPool = (ExecutorService) servletContext.getAttribute("threadPoolAlias");
final Future<ResultOfMyTask> myTask = threadPool.submit(callable);;
您应该存储对myTask的引用,并可以从其他线程查询它以查明它是否已完成以及结果是什么。
希望这会有所帮助......
答案 1 :(得分:0)
对于您的用例,您可以利用java平台中提供的Timer和TimerTask定期执行后台任务。
import java.util.Timer;
import java.util.TimerTask;
TimerTask dbTask = new TimerTask() {
@Override
public void run() {
// Perform Db call and do some action
}
};
final long INTERVAL = 1000 * 60 * 5; // five minute interval
// Run the task at fixed interval
new Timer(true).scheduleAtFixedRate(dbTask, 0, INTERVAL);
注意,如果任务完成时间超过五分钟,则后续任务将不会并行执行。相反,他们会在队列中等待,并在前一个完成后一个接一个地快速执行。
您可以将此代码包装在单个类中,并从启动servlet调用。
通常,在Servlet容器之外执行这些类型的定期后台作业是一种很好的做法,因为它们应该有效地用于HTTP请求。
答案 2 :(得分:0)
对于简单的后台任务,您根本不需要任何类型的线程池。您所需要做的就是:
编写一个ServletContextListener
,启动一个线程来执行您在实现Runnable
的类中定义的上述步骤。在contextDestroyed
方法中,设置一个标志,触发上面#2和#5中指示的检查,然后调用Thread.interrupt
,以便线程终止。
您的后台任务绝对不应尝试同步向客户端发送消息。相反,在监视器上使用某些其他机制(例如Object.notify
)通知等待轮询器(由于您不希望阻止客户端检查当前状态,因此没有任何意义),更新某种时间戳,或者让polling-clients检查#4中的当前数据。