java - 任务监视器 Servlet - 并发问题

标签 java jakarta-ee servlets concurrency ejb

设置:

我试图在我的 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 questionLock 注释的要点.


解决方案:

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/

相关文章:

java - 如何在 java 中创建 gnome gtk 通知?

Java 1.8 拒绝解析带有美国/阿留申 (HAST) 时区的日期?

java - 动态配置 Message Driven Bean 中的 MappedName 注解

tomcat - 部署到tomcat服务器时出现JPA错误

tomcat - Apache Tomcat 替换根 servlet 处理程序

java - 这会在 JVM 中被垃圾收集吗?

java - Java 应用程序中的 PKIX 路径构建失败,用于自签名 SSL 证书

java - ZKSpring 如何从 Spring MVC Controller 向 ZK zul 传递变量?

java - Java EE 应用程序的理想错误页面

java - 如何实现自定义JSF组件来绘制图表?