Task Monitor Servlet - 并发问题

时间:2014-09-11 19:24:49

标签: java java-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会立即返回适当的时间戳/ 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

我尝试过的事情:

  • 删除任务“状态”
  • 上的“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)的并发访问的所有方法。

1 个答案:

答案 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)可以克服这种情况。