【问题标题】:Task Monitor Servlet - concurrency issue任务监视器 Servlet - 并发问题
【发布时间】: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


    【解决方案1】:

    在 EJB 环境中显式使用线程通常不好。

    它们填充/污染服务器并可能失控,从而导致服务器出现问题,因为它们不受 EJB 容器的控制。

    例如,更好的解决方案是在单例方法上使用@Asynchronous 注解。有了这个,您可以启动异步任务而不会出现服务器问题。

    编辑:原因,doGet() 方法阻塞的原因:

    当调度程序调用 EJB 的 run() 方法时,它将锁定整个单例 EJB,因为写保护是默认行为。输入run() 后,Task 对象的run() 方法将调用Thread.sleep(...)。同时EJB的getState()方法会被阻塞,直到睡眠结束,从而阻塞了WebServlet的doGet()方法。

    正如 OP 在稍后的评论中所说,可以通过在 Singleton 的 run() 方法(以及 getState() 上方)上方使用注释 @Lock(LockType.READ) 来克服这种情况。

    【讨论】:

    • 我认为我没有使用显式线程;在我调用 Task.run() 的地方,我希望它是内联执行的。
    • 啊,是的 - 我被 Runnable 界面弄糊涂了。请给我一个提示,为什么 Task 类需要是 Runnable?
    • 只是巧合;它没有任何用途,但它显示了我的想法:)
    • 当一个Task对象被Singleton EJB创建并运行时,它会在运行EJB的Thread中运行,对吧?因此,当 Task 对象调用 Thread.sleep 时,EJB 将处于休眠状态。因此,访问 Singleton 的 getState() 方法将强制 doGet 等待,直到休眠结束。如果您同意,我会将此评论添加到答案中,以便您接受。
    • 这是一个虚拟测试用例,因此 EJB 线程在其执行的大部分时间都处于休眠状态。我将使用实际测试运行的一些细节来更新问题。
    猜你喜欢
    • 2023-04-03
    • 1970-01-01
    • 1970-01-01
    • 2016-05-13
    • 1970-01-01
    • 2020-01-16
    • 1970-01-01
    • 2017-11-28
    • 1970-01-01
    相关资源
    最近更新 更多