我正在尝试在servlet响应中显示计划任务的进度。我有一个简单的测试设置,使用三个类来“增加状态”任务20秒(每分钟4秒):
调度程序:
import javax.annotation.PostConstruct;
import javax.ejb.Schedule;
import javax.ejb.Singleton;
@Singleton
public class TaskScheduler {
private Task task;
@PostConstruct
public void init() {
task = new Task();
}
@Schedule(hour="*", minute="*", second="0")
public void run() {
(task = new Task()).run(); // no new Thread, this runs in-line
}
public String getState() {
return task.getState();
}
}
任务:
import java.util.Date;
public class Task implements Runnable {
private volatile String state = String.format("%s: %s\n",
Thread.currentThread().getName(),
new Date());
public String getState() {
return state;
}
@Override
public void run() {
long end = System.currentTimeMillis() + 20000;
while (System.currentTimeMillis() < end) {
String s = Thread.currentThread().getName();
try {
Thread.sleep(4000);
} catch (InterruptedException ex) {
s = ex.getMessage();
}
state += String.format("%s: %s\n",
s,
new Date());
}
}
}
的Servlet
import java.io.IOException;
import java.util.Date;
import javax.ejb.EJB;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@WebServlet("/simple")
public class SimpleServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@EJB
private TaskScheduler scheduler;
private String prefix = String.format("%s constructed at %s\n",
Thread.currentThread().getName(),
new Date());
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
prefix += String.format("%s served at %s\n",
Thread.currentThread().getName(),
new Date());
String s = String.format("%s%s",
prefix,
scheduler.getState());
resp.getOutputStream().write(s.getBytes());
}
}
<小时/>
当任务空闲时,doGet会立即返回适当的时间戳/ etc,但是当任务正在进行时,它会被延迟,就像阻止访问任务的状态一样。
以下是我在延迟期间从浏览器中复制的一些实际示例输出:
http-listener-1(3)于2014-09-11 17:01:36.600建成 http-listener-1(3)于2014-09-11 17:01:36.601发表 http-listener-1(3)发表于2014-09-11 17:01:36.601
http-listener-1(1)发表于2014-09-11 17:01:56.174
http-listener-1(2)发表于2014-09-11 17:01:57.541
http-listener-1(4)发表于2014-09-11 17:01:58.558
http-listener-1(3)发表于2014-09-11 17:01:59.444
http-listener-1(3):2014-09-11 17:01:36.603
这是延迟后输出的结果(全部一次):
http-listener-1(3)于2014-09-11 17:01:36.600建成 http-listener-1(3)于2014-09-11 17:01:36.601发表 http-listener-1(3)发表于2014-09-11 17:01:36.601
http-listener-1(1)发表于2014-09-11 17:01:56.174
http-listener-1(2)发表于2014-09-11 17:01:57.541
http-listener-1(4)发表于2014-09-11 17:01:58.558
http-listener-1(3)发表于2014-09-11 17:01:59.444
http-listener-1(5)发表于2014-09-11 17:02:00.502
__ejb-thread-pool2:2014-09-11 17:02:00.004
__ejb-thread-pool2:2014-09-11 17:02:04.005
__ejb-thread-pool2:2014-09-11 17:02:08.006
__ejb-thread-pool2:2014-09-11 17:02:12.006
__ejb-thread-pool2:2014-09-11 17:02:16.006
我尝试过的事情:
我正在部署到本地Glassfish服务器(版本4.0,以匹配我的目标环境)。我了解了如何使用this SO question中的@Schedule
注释以及来自this SO question的Lock
注释的要点。
<小时/>
Singleton
个类默认为@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
,其所有方法默认为@Lock(LockType.WRITE)
。当执行进入LockType.WRITE
方法时,它会导致执行任何其他方法等待。您可以使用@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
在类级别覆盖此值,也可以通过注释适用于@Lock(LockType.READ)
的并发访问的所有方法。
答案 0 :(得分:1)
在EJB环境中,显式地使用线程通常没有用处。
它们填充/轮询服务器并可能失控,导致服务器出现问题,因为它们不受EJB容器的控制。
更好的解决方案是在单例方法上使用@Asynchronous注释。通过这种方式,您可以在没有服务器问题的情况下启动异步任务。
编辑:原因,为什么doGet()方法阻止:
当Scheduler调用EJB的run()
方法时,它会将Singleton EJB作为一个整体锁定,因为写保护是默认行为。输入run()
后,Task
对象的run()
方法将被称为调用Thread.sleep(...)
。同时,EJB的getState()方法将被阻塞,直到休眠完成,从而阻塞了WebServlet的doGet()方法。
正如OP在后面的评论中所说,通过在Singleton的run()
方法(以及getState()
以上)上使用注释@Lock(LockType.READ)可以克服这种情况。