【发布时间】:2014-11-05 21:07:47
【问题描述】:
设置:
我试图在我的 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 会立即返回并带有适当的时间戳/等,但当任务正在进行时,它会延迟,就像阻止访问任务的状态一样。
这是我在延迟期间从浏览器复制的一些实际示例输出:
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
我尝试过的事情:
- 移除 Task 的“状态”中的“volatile”关键字
- 将 `@Lock(LockType.READ)` 添加到调度程序的 getState 方法中
- 将 `@Asynchronous` 添加到调度程序的运行方法中
我正在部署到本地 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) 注释适合并发访问的 all 方法来覆盖它。
【问题讨论】:
标签: java jakarta-ee servlets concurrency ejb