8

简短的问题:在 Tomcat 6 应用程序中 - 我如何运行(单独的)线程池?

运行线程池的最佳解决方案是什么?

长问题:
我在这里有一个简单的需求;
轮询数据库以获取某些数据,同时允许 Web 客户端等待答案(长轮询连接)。
当这些数据在数据库中可用时,我将向相关客户发送回复。

这么说,我现在不想深入研究任何框架(quartz scheduler也许?)。

因此,正如我总结的那样,我需要一个线程池来在后台完成这项工作。

所以如果Thread是我要使用的(实际上Runnable),哪个类可以组织这一切?有什么ThreadPool解决办法吗?有什么推荐吗?

4

3 回答 3

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

有关ExecutorsJVM 的更多一般信息,您可能需要阅读以下 Oracle 教程:http ://docs.oracle.com/javase/tutorial/essential/concurrency/executors.html

因此,要在 Tomcat 下使用线程池 ( ExecutorService),您应该执行以下操作:

.1。web.xml如果尚未完成,则在接口实例中创建和注册javax.servlet.ServletContextListener(它就像您的 Web 应用程序的入口点)。

.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();
}

.3. 在方法中的某处Servlet.service或您的 webapp 中的任何位置(您应该能够ServletContext从 webapp 获得对几乎任何地方的引用):

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 的引用,并可以从其他线程查询它以了解它是否已完成以及结果是什么。

希望这可以帮助...

于 2012-06-15T14:56:36.553 回答
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 请求。

于 2012-06-15T14:11:57.430 回答
0

对于一个简单的后台任务,您根本不需要任何类型的线程池。您需要做的就是:

  1. 启动线程
  2. 检查后台进程是否应该停止
  3. 让它轮询数据库
  4. 将新轮询的数据存储在可访问的地方
  5. 检查后台进程是否应该停止
  6. 去睡觉
  7. 重复步骤#2-#7

编写一个ServletContextListener启动线程以执行您在实现的类中定义的上述步骤Runnable。在该contextDestroyed方法中,设置一个标志来触发上面在#2 和#5 中指示的检查,然后调用Thread.interrupt以便您的线程终止。

您的后台任务绝对不应该尝试同步向客户端发送消息。相反,使用监视器上的其他机制通知等待轮询Object.notify器(这实际上没有任何意义,因为您不想阻止客户端检查当前状态),更新某种时间戳,或者只是进行轮询-客户端检查#4 中可用的当前数据。

于 2012-06-15T20:10:27.350 回答