设置:
我试图在我的 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) constructed at 2014-09-11 17:01:36.600
http-listener-1(3) inited at 2014-09-11 17:01:36.601
http-listener-1(3) served at 2014-09-11 17:01:36.601
http-listener-1(1) served at 2014-09-11 17:01:56.174
http-listener-1(2) served at 2014-09-11 17:01:57.541
http-listener-1(4) served at 2014-09-11 17:01:58.558
http-listener-1(3) served at 2014-09-11 17:01:59.444
http-listener-1(3): 2014-09-11 17:01:36.603
这是延迟后(同时)出现的输出:
http-listener-1(3) constructed at 2014-09-11 17:01:36.600
http-listener-1(3) inited at 2014-09-11 17:01:36.601
http-listener-1(3) served at 2014-09-11 17:01:36.601
http-listener-1(1) served at 2014-09-11 17:01:56.174
http-listener-1(2) served at 2014-09-11 17:01:57.541
http-listener-1(4) served at 2014-09-11 17:01:58.558
http-listener-1(3) served at 2014-09-11 17:01:59.444
http-listener-1(5) served at 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
我尝试过的事情:
- 删除任务“状态”上的“ volatile ”关键字
- 将 `@Lock(LockType.READ)` 添加到 Scheduler 的 getState 方法
- 将 `@Asynchronous` 添加到 Scheduler 的 run 方法
我正在部署到本地 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)
注释适合并发访问的所有方法。
最佳答案
在 EJB 环境中显式使用线程通常没有什么好处。
它们填充/污染服务器并可能失控,导致服务器出现问题,因为它们不受 EJB 容器控制。
更好的解决方案是在单例的方法上使用 @Asynchronous 注释。这样您就可以启动异步任务,而不会对服务器造成任何问题。
编辑:原因,doGet() 方法阻塞的原因:
当调度程序调用 EJB 的 run()
方法时,它将锁定整个 Singleton EJB,因为写保护是默认行为。输入 run()
后,Task
对象的 run()
方法将被调用,从而调用 Thread.sleep(...)
。同时,EJB 的 getState() 方法将被阻塞,直到 hibernate 完成,从而阻塞 WebServlet 的 doGet() 方法。
正如OP在后面的评论中所说,可以通过在单例的run()
方法上方(以及在getState()
上方)使用注释@Lock(LockType.READ)来克服这种情况。
关于java - 任务监视器 Servlet - 并发问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25795541/